mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 09:21:04 +00:00
dispute statements node side limiting (#4541)
This commit is contained in:
committed by
GitHub
parent
b0a4f78edc
commit
fad55b95fa
Generated
+2
@@ -6315,11 +6315,13 @@ dependencies = [
|
|||||||
"bitvec",
|
"bitvec",
|
||||||
"futures 0.3.18",
|
"futures 0.3.18",
|
||||||
"futures-timer 3.0.2",
|
"futures-timer 3.0.2",
|
||||||
|
"polkadot-node-primitives",
|
||||||
"polkadot-node-subsystem",
|
"polkadot-node-subsystem",
|
||||||
"polkadot-node-subsystem-test-helpers",
|
"polkadot-node-subsystem-test-helpers",
|
||||||
"polkadot-node-subsystem-util",
|
"polkadot-node-subsystem-util",
|
||||||
"polkadot-primitives",
|
"polkadot-primitives",
|
||||||
"polkadot-primitives-test-helpers",
|
"polkadot-primitives-test-helpers",
|
||||||
|
"rand 0.8.4",
|
||||||
"sp-application-crypto",
|
"sp-application-crypto",
|
||||||
"sp-keystore",
|
"sp-keystore",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ futures = "0.3.17"
|
|||||||
tracing = "0.1.29"
|
tracing = "0.1.29"
|
||||||
thiserror = "1.0.30"
|
thiserror = "1.0.30"
|
||||||
polkadot-primitives = { path = "../../../primitives" }
|
polkadot-primitives = { path = "../../../primitives" }
|
||||||
|
polkadot-node-primitives = { path = "../../primitives" }
|
||||||
polkadot-node-subsystem = { path = "../../subsystem" }
|
polkadot-node-subsystem = { path = "../../subsystem" }
|
||||||
polkadot-node-subsystem-util = { path = "../../subsystem-util" }
|
polkadot-node-subsystem-util = { path = "../../subsystem-util" }
|
||||||
futures-timer = "3.0.2"
|
futures-timer = "3.0.2"
|
||||||
|
rand = "0.8.4"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
sp-application-crypto = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
sp-application-crypto = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ use futures::{
|
|||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
use futures_timer::Delay;
|
use futures_timer::Delay;
|
||||||
|
use polkadot_node_primitives::CandidateVotes;
|
||||||
use polkadot_node_subsystem::{
|
use polkadot_node_subsystem::{
|
||||||
errors::{ChainApiError, RuntimeApiError},
|
errors::{ChainApiError, RuntimeApiError},
|
||||||
jaeger,
|
jaeger,
|
||||||
@@ -39,11 +40,14 @@ use polkadot_node_subsystem_util::{
|
|||||||
JobSubsystem, JobTrait,
|
JobSubsystem, JobTrait,
|
||||||
};
|
};
|
||||||
use polkadot_primitives::v1::{
|
use polkadot_primitives::v1::{
|
||||||
BackedCandidate, BlockNumber, CandidateReceipt, CoreState, DisputeStatement,
|
BackedCandidate, BlockNumber, CandidateHash, CandidateReceipt, CoreState, DisputeStatement,
|
||||||
DisputeStatementSet, Hash, MultiDisputeStatementSet, OccupiedCoreAssumption,
|
DisputeStatementSet, Hash, MultiDisputeStatementSet, OccupiedCoreAssumption, SessionIndex,
|
||||||
SignedAvailabilityBitfield, ValidatorIndex,
|
SignedAvailabilityBitfield, ValidatorIndex,
|
||||||
};
|
};
|
||||||
use std::{collections::BTreeMap, pin::Pin};
|
use std::{
|
||||||
|
collections::{BTreeMap, HashSet},
|
||||||
|
pin::Pin,
|
||||||
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
mod metrics;
|
mod metrics;
|
||||||
@@ -490,7 +494,7 @@ async fn select_candidates(
|
|||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
"Selected {} candidates for {} cores",
|
"Selected {} candidates for {} cores",
|
||||||
candidates.len(),
|
candidates.len(),
|
||||||
availability_cores.len(),
|
availability_cores.len()
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(candidates)
|
Ok(candidates)
|
||||||
@@ -551,55 +555,128 @@ fn bitfields_indicate_availability(
|
|||||||
3 * availability.count_ones() >= 2 * availability.len()
|
3 * availability.count_ones() >= 2 * availability.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn select_disputes(
|
#[derive(Debug)]
|
||||||
sender: &mut impl SubsystemSender,
|
enum RequestType {
|
||||||
metrics: &metrics::Metrics,
|
/// Query recent disputes, could be an excessive amount.
|
||||||
) -> Result<MultiDisputeStatementSet, Error> {
|
Recent,
|
||||||
let (tx, rx) = oneshot::channel();
|
/// Query the currently active and very recently concluded disputes.
|
||||||
|
Active,
|
||||||
|
}
|
||||||
|
|
||||||
// We use `RecentDisputes` instead of `ActiveDisputes` because redundancy is fine.
|
/// Request open disputes identified by `CandidateHash` and the `SessionIndex`.
|
||||||
// It's heavier than `ActiveDisputes` but ensures that everything from the dispute
|
async fn request_disputes(
|
||||||
// window gets on-chain, unlike `ActiveDisputes`.
|
sender: &mut impl SubsystemSender,
|
||||||
//
|
active_or_recent: RequestType,
|
||||||
// This should have no meaningful impact on performance on production networks for
|
) -> Vec<(SessionIndex, CandidateHash)> {
|
||||||
// two reasons:
|
let (tx, rx) = oneshot::channel();
|
||||||
// 1. In large validator sets, a node might be a block author 1% or less of the time.
|
let msg = match active_or_recent {
|
||||||
// this code-path only triggers in the case of being a block author.
|
RequestType::Recent => DisputeCoordinatorMessage::RecentDisputes(tx),
|
||||||
// 2. Disputes are expected to be rare because they come with heavy slashing.
|
RequestType::Active => DisputeCoordinatorMessage::ActiveDisputes(tx),
|
||||||
sender.send_message(DisputeCoordinatorMessage::RecentDisputes(tx).into()).await;
|
};
|
||||||
|
sender.send_message(msg.into()).await;
|
||||||
|
|
||||||
let recent_disputes = match rx.await {
|
let recent_disputes = match rx.await {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(oneshot::Canceled) => {
|
Err(oneshot::Canceled) => {
|
||||||
tracing::debug!(
|
tracing::warn!(target: LOG_TARGET, "Unable to gather {:?} disputes", active_or_recent);
|
||||||
target: LOG_TARGET,
|
|
||||||
"Unable to gather recent disputes - subsystem disconnected?",
|
|
||||||
);
|
|
||||||
|
|
||||||
Vec::new()
|
Vec::new()
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
recent_disputes
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Request the relevant dispute statements for a set of disputes identified by `CandidateHash` and the `SessionIndex`.
|
||||||
|
async fn request_votes(
|
||||||
|
sender: &mut impl SubsystemSender,
|
||||||
|
disputes_to_query: Vec<(SessionIndex, CandidateHash)>,
|
||||||
|
) -> Vec<(SessionIndex, CandidateHash, CandidateVotes)> {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
sender
|
||||||
|
.send_message(DisputeCoordinatorMessage::QueryCandidateVotes(disputes_to_query, tx).into())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match rx.await {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(oneshot::Canceled) => {
|
||||||
|
tracing::warn!(target: LOG_TARGET, "Unable to query candidate votes");
|
||||||
|
Vec::new()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extend `acc` by `n` random, picks of not-yet-present in `acc` items of `recent` without repetition and additions of recent.
|
||||||
|
fn extend_by_random_subset_without_repetition(
|
||||||
|
acc: &mut Vec<(SessionIndex, CandidateHash)>,
|
||||||
|
extension: Vec<(SessionIndex, CandidateHash)>,
|
||||||
|
n: usize,
|
||||||
|
) {
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
let lut = acc.iter().cloned().collect::<HashSet<(SessionIndex, CandidateHash)>>();
|
||||||
|
|
||||||
|
let mut unique_new =
|
||||||
|
extension.into_iter().filter(|recent| !lut.contains(recent)).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// we can simply add all
|
||||||
|
if unique_new.len() <= n {
|
||||||
|
acc.extend(unique_new)
|
||||||
|
} else {
|
||||||
|
acc.reserve(n);
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
for _ in 0..n {
|
||||||
|
let idx = rng.gen_range(0..unique_new.len());
|
||||||
|
acc.push(unique_new.swap_remove(idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// assure sorting stays candid according to session index
|
||||||
|
acc.sort_unstable_by(|a, b| a.0.cmp(&b.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn select_disputes(
|
||||||
|
sender: &mut impl SubsystemSender,
|
||||||
|
metrics: &metrics::Metrics,
|
||||||
|
) -> Result<MultiDisputeStatementSet, Error> {
|
||||||
|
const MAX_DISPUTES_FORWARDED_TO_RUNTIME: usize = 10_000;
|
||||||
|
|
||||||
|
// We use `RecentDisputes` instead of `ActiveDisputes` because redundancy is fine.
|
||||||
|
// It's heavier than `ActiveDisputes` but ensures that everything from the dispute
|
||||||
|
// window gets on-chain, unlike `ActiveDisputes`.
|
||||||
|
// In case of an overload condition, we limit ourselves to active disputes, and fill up to the
|
||||||
|
// upper bound of disputes to pass to wasm `fn create_inherent_data`.
|
||||||
|
// If the active ones are already exceeding the bounds, randomly select a subset.
|
||||||
|
let recent = request_disputes(sender, RequestType::Recent).await;
|
||||||
|
let disputes = if recent.len() > MAX_DISPUTES_FORWARDED_TO_RUNTIME {
|
||||||
|
tracing::warn!(
|
||||||
|
target: LOG_TARGET,
|
||||||
|
"Recent disputes are excessive ({} > {}), reduce to active ones, and selected",
|
||||||
|
recent.len(),
|
||||||
|
MAX_DISPUTES_FORWARDED_TO_RUNTIME
|
||||||
|
);
|
||||||
|
let mut active = request_disputes(sender, RequestType::Active).await;
|
||||||
|
let n_active = active.len();
|
||||||
|
let active = if active.len() > MAX_DISPUTES_FORWARDED_TO_RUNTIME {
|
||||||
|
let mut picked = Vec::with_capacity(MAX_DISPUTES_FORWARDED_TO_RUNTIME);
|
||||||
|
extend_by_random_subset_without_repetition(
|
||||||
|
&mut picked,
|
||||||
|
active,
|
||||||
|
MAX_DISPUTES_FORWARDED_TO_RUNTIME,
|
||||||
|
);
|
||||||
|
picked
|
||||||
|
} else {
|
||||||
|
extend_by_random_subset_without_repetition(
|
||||||
|
&mut active,
|
||||||
|
recent,
|
||||||
|
MAX_DISPUTES_FORWARDED_TO_RUNTIME.saturating_sub(n_active),
|
||||||
|
);
|
||||||
|
active
|
||||||
|
};
|
||||||
|
active
|
||||||
|
} else {
|
||||||
|
recent
|
||||||
|
};
|
||||||
|
|
||||||
// Load all votes for all disputes from the coordinator.
|
// Load all votes for all disputes from the coordinator.
|
||||||
let dispute_candidate_votes = {
|
let dispute_candidate_votes = request_votes(sender, disputes).await;
|
||||||
let (tx, rx) = oneshot::channel();
|
|
||||||
sender
|
|
||||||
.send_message(
|
|
||||||
DisputeCoordinatorMessage::QueryCandidateVotes(recent_disputes, tx).into(),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match rx.await {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(oneshot::Canceled) => {
|
|
||||||
tracing::debug!(
|
|
||||||
target: LOG_TARGET,
|
|
||||||
"Unable to query candidate votes - subsystem disconnected?",
|
|
||||||
);
|
|
||||||
Vec::new()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Transform all `CandidateVotes` into `MultiDisputeStatementSet`.
|
// Transform all `CandidateVotes` into `MultiDisputeStatementSet`.
|
||||||
Ok(dispute_candidate_votes
|
Ok(dispute_candidate_votes
|
||||||
|
|||||||
Reference in New Issue
Block a user