mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-25 04:45:42 +00:00
More secure Signed implementation (#2963)
* Remove signature verification in backing. `SignedFullStatement` now signals that the signature has already been checked. * Remove unused check_payload function. * Introduced unchecked signed variants. * Fix inclusion to use unchecked variant. * More unchecked variants. * Use unchecked variants in protocols. * Start fixing statement-distribution. * Fixup statement distribution. * Fix inclusion. * Fix warning. * Fix backing properly. * Fix bitfield distribution. * Make crypto store optional for `RuntimeInfo`. * Factor out utility functions. * get_group_rotation_info * WIP: Collator cleanup + check signatures. * Convenience signature checking functions. * Check signature on collator-side. * Fix warnings. * Fix collator side tests. * Get rid of warnings. * Better Signed/UncheckedSigned implementation. Also get rid of Encode/Decode for Signed! *party* * Get rid of dead code. * Move Signed in its own module. * into_checked -> try_into_checked * Fix merge.
This commit is contained in:
Generated
-1
@@ -5582,7 +5582,6 @@ dependencies = [
|
|||||||
"futures 0.3.14",
|
"futures 0.3.14",
|
||||||
"log",
|
"log",
|
||||||
"maplit",
|
"maplit",
|
||||||
"parity-scale-codec",
|
|
||||||
"polkadot-node-network-protocol",
|
"polkadot-node-network-protocol",
|
||||||
"polkadot-node-subsystem",
|
"polkadot-node-subsystem",
|
||||||
"polkadot-node-subsystem-test-helpers",
|
"polkadot-node-subsystem-test-helpers",
|
||||||
|
|||||||
@@ -195,7 +195,6 @@ const fn group_quorum(n_validators: usize) -> usize {
|
|||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct TableContext {
|
struct TableContext {
|
||||||
signing_context: SigningContext,
|
|
||||||
validator: Option<Validator>,
|
validator: Option<Validator>,
|
||||||
groups: HashMap<ParaId, Vec<ValidatorIndex>>,
|
groups: HashMap<ParaId, Vec<ValidatorIndex>>,
|
||||||
validators: Vec<ValidatorId>,
|
validators: Vec<ValidatorId>,
|
||||||
@@ -870,7 +869,6 @@ impl CandidateBackingJob {
|
|||||||
.with_candidate(statement.payload().candidate_hash())
|
.with_candidate(statement.payload().candidate_hash())
|
||||||
.with_relay_parent(_relay_parent);
|
.with_relay_parent(_relay_parent);
|
||||||
|
|
||||||
self.check_statement_signature(&statement)?;
|
|
||||||
match self.maybe_validate_and_import(&root_span, sender, statement).await {
|
match self.maybe_validate_and_import(&root_span, sender, statement).await {
|
||||||
Err(Error::ValidationFailed(_)) => return Ok(()),
|
Err(Error::ValidationFailed(_)) => return Ok(()),
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
@@ -1028,22 +1026,6 @@ impl CandidateBackingJob {
|
|||||||
Some(signed)
|
Some(signed)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip(self), fields(subsystem = LOG_TARGET))]
|
|
||||||
fn check_statement_signature(&self, statement: &SignedFullStatement) -> Result<(), Error> {
|
|
||||||
let idx = statement.validator_index().0 as usize;
|
|
||||||
|
|
||||||
if self.table_context.validators.len() > idx {
|
|
||||||
statement.check_signature(
|
|
||||||
&self.table_context.signing_context,
|
|
||||||
&self.table_context.validators[idx],
|
|
||||||
).map_err(|_| Error::InvalidSignature)?;
|
|
||||||
} else {
|
|
||||||
return Err(Error::InvalidSignature);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Insert or get the unbacked-span for the given candidate hash.
|
/// Insert or get the unbacked-span for the given candidate hash.
|
||||||
fn insert_or_get_unbacked_span(
|
fn insert_or_get_unbacked_span(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -1204,7 +1186,6 @@ impl util::JobTrait for CandidateBackingJob {
|
|||||||
let table_context = TableContext {
|
let table_context = TableContext {
|
||||||
groups,
|
groups,
|
||||||
validators,
|
validators,
|
||||||
signing_context,
|
|
||||||
validator,
|
validator,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1658,14 +1639,9 @@ mod tests {
|
|||||||
AllMessages::StatementDistribution(
|
AllMessages::StatementDistribution(
|
||||||
StatementDistributionMessage::Share(
|
StatementDistributionMessage::Share(
|
||||||
parent_hash,
|
parent_hash,
|
||||||
signed_statement,
|
_signed_statement,
|
||||||
)
|
)
|
||||||
) if parent_hash == test_state.relay_parent => {
|
) if parent_hash == test_state.relay_parent => {}
|
||||||
signed_statement.check_signature(
|
|
||||||
&test_state.signing_context,
|
|
||||||
&test_state.validator_public[0],
|
|
||||||
).unwrap();
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
@@ -1708,11 +1684,6 @@ mod tests {
|
|||||||
}.build();
|
}.build();
|
||||||
|
|
||||||
let candidate_a_hash = candidate_a.hash();
|
let candidate_a_hash = candidate_a.hash();
|
||||||
let public0 = CryptoStore::sr25519_generate_new(
|
|
||||||
&*test_state.keystore,
|
|
||||||
ValidatorId::ID,
|
|
||||||
Some(&test_state.validators[0].to_seed()),
|
|
||||||
).await.expect("Insert key into keystore");
|
|
||||||
let public1 = CryptoStore::sr25519_generate_new(
|
let public1 = CryptoStore::sr25519_generate_new(
|
||||||
&*test_state.keystore,
|
&*test_state.keystore,
|
||||||
ValidatorId::ID,
|
ValidatorId::ID,
|
||||||
@@ -1795,10 +1766,9 @@ mod tests {
|
|||||||
assert_matches!(
|
assert_matches!(
|
||||||
virtual_overseer.recv().await,
|
virtual_overseer.recv().await,
|
||||||
AllMessages::StatementDistribution(
|
AllMessages::StatementDistribution(
|
||||||
StatementDistributionMessage::Share(hash, stmt)
|
StatementDistributionMessage::Share(hash, _stmt)
|
||||||
) => {
|
) => {
|
||||||
assert_eq!(test_state.relay_parent, hash);
|
assert_eq!(test_state.relay_parent, hash);
|
||||||
stmt.check_signature(&test_state.signing_context, &public0.into()).expect("Is signed correctly");
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2092,11 +2062,6 @@ mod tests {
|
|||||||
signed_statement,
|
signed_statement,
|
||||||
)
|
)
|
||||||
) if relay_parent == test_state.relay_parent => {
|
) if relay_parent == test_state.relay_parent => {
|
||||||
signed_statement.check_signature(
|
|
||||||
&test_state.signing_context,
|
|
||||||
&test_state.validator_public[0],
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(*signed_statement.payload(), Statement::Valid(candidate_a_hash));
|
assert_eq!(*signed_statement.payload(), Statement::Valid(candidate_a_hash));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -2257,11 +2222,6 @@ mod tests {
|
|||||||
signed_statement,
|
signed_statement,
|
||||||
)
|
)
|
||||||
) if parent_hash == test_state.relay_parent => {
|
) if parent_hash == test_state.relay_parent => {
|
||||||
signed_statement.check_signature(
|
|
||||||
&test_state.signing_context,
|
|
||||||
&test_state.validator_public[0],
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(*signed_statement.payload(), Statement::Seconded(candidate_b));
|
assert_eq!(*signed_statement.payload(), Statement::Seconded(candidate_b));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -2593,10 +2553,7 @@ mod tests {
|
|||||||
use sp_core::Encode;
|
use sp_core::Encode;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
let relay_parent = [1; 32].into();
|
|
||||||
let para_id = ParaId::from(10);
|
let para_id = ParaId::from(10);
|
||||||
let session_index = 5;
|
|
||||||
let signing_context = SigningContext { parent_hash: relay_parent, session_index };
|
|
||||||
let validators = vec![
|
let validators = vec![
|
||||||
Sr25519Keyring::Alice,
|
Sr25519Keyring::Alice,
|
||||||
Sr25519Keyring::Bob,
|
Sr25519Keyring::Bob,
|
||||||
@@ -2614,7 +2571,6 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let table_context = TableContext {
|
let table_context = TableContext {
|
||||||
signing_context,
|
|
||||||
validator: None,
|
validator: None,
|
||||||
groups: validator_groups,
|
groups: validator_groups,
|
||||||
validators: validator_public.clone(),
|
validators: validator_public.clone(),
|
||||||
|
|||||||
@@ -249,12 +249,12 @@ impl CandidateSelectionJob {
|
|||||||
.with_relay_parent(_relay_parent);
|
.with_relay_parent(_relay_parent);
|
||||||
self.handle_invalid(sender, candidate_receipt).await;
|
self.handle_invalid(sender, candidate_receipt).await;
|
||||||
}
|
}
|
||||||
Some(CandidateSelectionMessage::Seconded(_relay_parent, statement)) => {
|
Some(CandidateSelectionMessage::Seconded(relay_parent, statement)) => {
|
||||||
let _span = span.child("handle-seconded")
|
let _span = span.child("handle-seconded")
|
||||||
.with_stage(jaeger::Stage::CandidateSelection)
|
.with_stage(jaeger::Stage::CandidateSelection)
|
||||||
.with_candidate(statement.payload().candidate_hash())
|
.with_candidate(statement.payload().candidate_hash())
|
||||||
.with_relay_parent(_relay_parent);
|
.with_relay_parent(relay_parent);
|
||||||
self.handle_seconded(sender, statement).await;
|
self.handle_seconded(sender, relay_parent, statement).await;
|
||||||
}
|
}
|
||||||
None => break,
|
None => break,
|
||||||
}
|
}
|
||||||
@@ -345,6 +345,7 @@ impl CandidateSelectionJob {
|
|||||||
async fn handle_seconded(
|
async fn handle_seconded(
|
||||||
&mut self,
|
&mut self,
|
||||||
sender: &mut impl SubsystemSender,
|
sender: &mut impl SubsystemSender,
|
||||||
|
relay_parent: Hash,
|
||||||
statement: SignedFullStatement,
|
statement: SignedFullStatement,
|
||||||
) {
|
) {
|
||||||
let received_from = match &self.seconded_candidate {
|
let received_from = match &self.seconded_candidate {
|
||||||
@@ -368,7 +369,11 @@ impl CandidateSelectionJob {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
sender.send_message(
|
sender.send_message(
|
||||||
CollatorProtocolMessage::NotifyCollationSeconded(received_from.clone(), statement).into()
|
CollatorProtocolMessage::NotifyCollationSeconded(
|
||||||
|
received_from.clone(),
|
||||||
|
relay_parent,
|
||||||
|
statement
|
||||||
|
).into()
|
||||||
).await;
|
).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ impl ParachainsInherentDataProvider {
|
|||||||
|
|
||||||
let inherent_data = match res {
|
let inherent_data = match res {
|
||||||
Ok(pd) => ParachainsInherentData {
|
Ok(pd) => ParachainsInherentData {
|
||||||
bitfields: pd.bitfields,
|
bitfields: pd.bitfields.into_iter().map(Into::into).collect(),
|
||||||
backed_candidates: pd.backed_candidates,
|
backed_candidates: pd.backed_candidates,
|
||||||
disputes: pd.disputes,
|
disputes: pd.disputes,
|
||||||
parent_header,
|
parent_header,
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ impl AvailabilityDistributionSubsystem {
|
|||||||
|
|
||||||
/// Create a new instance of the availability distribution.
|
/// Create a new instance of the availability distribution.
|
||||||
pub fn new(keystore: SyncCryptoStorePtr, metrics: Metrics) -> Self {
|
pub fn new(keystore: SyncCryptoStorePtr, metrics: Metrics) -> Self {
|
||||||
let runtime = RuntimeInfo::new(keystore.clone());
|
let runtime = RuntimeInfo::new(Some(keystore.clone()));
|
||||||
Self { keystore, runtime, metrics }
|
Self { keystore, runtime, metrics }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ mod tests {
|
|||||||
let (mut context, mut virtual_overseer) =
|
let (mut context, mut virtual_overseer) =
|
||||||
test_helpers::make_subsystem_context::<AvailabilityDistributionMessage, TaskExecutor>(pool.clone());
|
test_helpers::make_subsystem_context::<AvailabilityDistributionMessage, TaskExecutor>(pool.clone());
|
||||||
let keystore = make_ferdie_keystore();
|
let keystore = make_ferdie_keystore();
|
||||||
let mut runtime = polkadot_node_subsystem_util::runtime::RuntimeInfo::new(keystore);
|
let mut runtime = polkadot_node_subsystem_util::runtime::RuntimeInfo::new(Some(keystore));
|
||||||
|
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
let testee = async {
|
let testee = async {
|
||||||
|
|||||||
@@ -32,14 +32,14 @@ use futures::{
|
|||||||
|
|
||||||
use sp_keystore::SyncCryptoStorePtr;
|
use sp_keystore::SyncCryptoStorePtr;
|
||||||
|
|
||||||
use polkadot_node_subsystem_util::request_availability_cores;
|
use polkadot_node_subsystem_util::runtime::get_occupied_cores;
|
||||||
use polkadot_primitives::v1::{CandidateHash, CoreState, Hash, OccupiedCore};
|
use polkadot_primitives::v1::{CandidateHash, Hash, OccupiedCore};
|
||||||
use polkadot_subsystem::{
|
use polkadot_subsystem::{
|
||||||
messages::AllMessages, ActiveLeavesUpdate, SubsystemContext, ActivatedLeaf,
|
messages::AllMessages, ActiveLeavesUpdate, SubsystemContext, ActivatedLeaf,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{error::recv_runtime, session_cache::SessionCache, LOG_TARGET, Metrics};
|
use super::{session_cache::SessionCache, LOG_TARGET, Metrics};
|
||||||
use crate::error::Error;
|
|
||||||
|
|
||||||
/// A task fetching a particular chunk.
|
/// A task fetching a particular chunk.
|
||||||
mod fetch_task;
|
mod fetch_task;
|
||||||
@@ -125,7 +125,7 @@ impl Requester {
|
|||||||
Context: SubsystemContext,
|
Context: SubsystemContext,
|
||||||
{
|
{
|
||||||
for ActivatedLeaf { hash: leaf, .. } in new_heads {
|
for ActivatedLeaf { hash: leaf, .. } in new_heads {
|
||||||
let cores = query_occupied_cores(ctx, leaf).await?;
|
let cores = get_occupied_cores(ctx, leaf).await?;
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
occupied_cores = ?cores,
|
occupied_cores = ?cores,
|
||||||
@@ -226,25 +226,3 @@ impl Stream for Requester {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Query all hashes and descriptors of candidates pending availability at a particular block.
|
|
||||||
#[tracing::instrument(level = "trace", skip(ctx), fields(subsystem = LOG_TARGET))]
|
|
||||||
async fn query_occupied_cores<Context>(
|
|
||||||
ctx: &mut Context,
|
|
||||||
relay_parent: Hash,
|
|
||||||
) -> Result<Vec<OccupiedCore>, Error>
|
|
||||||
where
|
|
||||||
Context: SubsystemContext,
|
|
||||||
{
|
|
||||||
let cores = recv_runtime(request_availability_cores(relay_parent, ctx.sender()).await).await?;
|
|
||||||
|
|
||||||
Ok(cores
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|core_state| {
|
|
||||||
if let CoreState::Occupied(occupied) = core_state {
|
|
||||||
Some(occupied)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ edition = "2018"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
futures = "0.3.12"
|
futures = "0.3.12"
|
||||||
tracing = "0.1.25"
|
tracing = "0.1.25"
|
||||||
parity-scale-codec = { version = "2.0.0", default-features = false, features = ["derive"] }
|
|
||||||
polkadot-primitives = { path = "../../../primitives" }
|
polkadot-primitives = { path = "../../../primitives" }
|
||||||
polkadot-subsystem = { package = "polkadot-node-subsystem", path = "../../subsystem" }
|
polkadot-subsystem = { package = "polkadot-node-subsystem", path = "../../subsystem" }
|
||||||
polkadot-node-subsystem-util = { path = "../../subsystem-util" }
|
polkadot-node-subsystem-util = { path = "../../subsystem-util" }
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
|
|
||||||
#![deny(unused_crate_dependencies)]
|
#![deny(unused_crate_dependencies)]
|
||||||
|
|
||||||
use parity_scale_codec::{Decode, Encode};
|
|
||||||
use futures::{channel::oneshot, FutureExt};
|
use futures::{channel::oneshot, FutureExt};
|
||||||
|
|
||||||
use polkadot_subsystem::messages::*;
|
use polkadot_subsystem::messages::*;
|
||||||
@@ -49,7 +48,7 @@ const BENEFIT_VALID_MESSAGE: Rep = Rep::BenefitMinor("Valid message");
|
|||||||
|
|
||||||
/// Checked signed availability bitfield that is distributed
|
/// Checked signed availability bitfield that is distributed
|
||||||
/// to other peers.
|
/// to other peers.
|
||||||
#[derive(Encode, Decode, Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
struct BitfieldGossipMessage {
|
struct BitfieldGossipMessage {
|
||||||
/// The relay parent this message is relative to.
|
/// The relay parent this message is relative to.
|
||||||
relay_parent: Hash,
|
relay_parent: Hash,
|
||||||
@@ -69,7 +68,7 @@ impl BitfieldGossipMessage {
|
|||||||
{
|
{
|
||||||
protocol_v1::BitfieldDistributionMessage::Bitfield(
|
protocol_v1::BitfieldDistributionMessage::Bitfield(
|
||||||
self.relay_parent,
|
self.relay_parent,
|
||||||
self.signed_availability,
|
self.signed_availability.into(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -392,19 +391,26 @@ async fn process_incoming_peer_message<Context>(
|
|||||||
state: &mut ProtocolState,
|
state: &mut ProtocolState,
|
||||||
metrics: &Metrics,
|
metrics: &Metrics,
|
||||||
origin: PeerId,
|
origin: PeerId,
|
||||||
message: BitfieldGossipMessage,
|
message: protocol_v1::BitfieldDistributionMessage,
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
Context: SubsystemContext<Message = BitfieldDistributionMessage>,
|
Context: SubsystemContext<Message = BitfieldDistributionMessage>,
|
||||||
{
|
{
|
||||||
|
let protocol_v1::BitfieldDistributionMessage::Bitfield(relay_parent, bitfield) = message;
|
||||||
|
tracing::trace!(
|
||||||
|
target: LOG_TARGET,
|
||||||
|
peer_id = %origin,
|
||||||
|
?relay_parent,
|
||||||
|
"received bitfield gossip from peer"
|
||||||
|
);
|
||||||
// we don't care about this, not part of our view.
|
// we don't care about this, not part of our view.
|
||||||
if !state.view.contains(&message.relay_parent) {
|
if !state.view.contains(&relay_parent) {
|
||||||
modify_reputation(ctx, origin, COST_NOT_IN_VIEW).await;
|
modify_reputation(ctx, origin, COST_NOT_IN_VIEW).await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore anything the overseer did not tell this subsystem to work on.
|
// Ignore anything the overseer did not tell this subsystem to work on.
|
||||||
let mut job_data = state.per_relay_parent.get_mut(&message.relay_parent);
|
let mut job_data = state.per_relay_parent.get_mut(&relay_parent);
|
||||||
let job_data: &mut _ = if let Some(ref mut job_data) = job_data {
|
let job_data: &mut _ = if let Some(ref mut job_data) = job_data {
|
||||||
job_data
|
job_data
|
||||||
} else {
|
} else {
|
||||||
@@ -412,17 +418,19 @@ where
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let validator_index = bitfield.unchecked_validator_index();
|
||||||
|
|
||||||
let mut _span = job_data.span
|
let mut _span = job_data.span
|
||||||
.child("msg-received")
|
.child("msg-received")
|
||||||
.with_peer_id(&origin)
|
.with_peer_id(&origin)
|
||||||
.with_claimed_validator_index(message.signed_availability.validator_index())
|
.with_claimed_validator_index(validator_index)
|
||||||
.with_stage(jaeger::Stage::BitfieldDistribution);
|
.with_stage(jaeger::Stage::BitfieldDistribution);
|
||||||
|
|
||||||
let validator_set = &job_data.validator_set;
|
let validator_set = &job_data.validator_set;
|
||||||
if validator_set.is_empty() {
|
if validator_set.is_empty() {
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
relay_parent = %message.relay_parent,
|
relay_parent = %relay_parent,
|
||||||
?origin,
|
?origin,
|
||||||
"Validator set is empty",
|
"Validator set is empty",
|
||||||
);
|
);
|
||||||
@@ -433,8 +441,7 @@ where
|
|||||||
// Use the (untrusted) validator index provided by the signed payload
|
// Use the (untrusted) validator index provided by the signed payload
|
||||||
// and see if that one actually signed the availability bitset.
|
// and see if that one actually signed the availability bitset.
|
||||||
let signing_context = job_data.signing_context.clone();
|
let signing_context = job_data.signing_context.clone();
|
||||||
let validator_index = message.signed_availability.validator_index().0 as usize;
|
let validator = if let Some(validator) = validator_set.get(validator_index.0 as usize) {
|
||||||
let validator = if let Some(validator) = validator_set.get(validator_index) {
|
|
||||||
validator.clone()
|
validator.clone()
|
||||||
} else {
|
} else {
|
||||||
modify_reputation(ctx, origin, COST_VALIDATOR_INDEX_INVALID).await;
|
modify_reputation(ctx, origin, COST_VALIDATOR_INDEX_INVALID).await;
|
||||||
@@ -454,7 +461,7 @@ where
|
|||||||
} else {
|
} else {
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
validator_index,
|
?validator_index,
|
||||||
?origin,
|
?origin,
|
||||||
"Duplicate message",
|
"Duplicate message",
|
||||||
);
|
);
|
||||||
@@ -468,22 +475,26 @@ where
|
|||||||
if let Some(old_message) = one_per_validator.get(&validator) {
|
if let Some(old_message) = one_per_validator.get(&validator) {
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
validator_index,
|
?validator_index,
|
||||||
"already received a message for validator",
|
"already received a message for validator",
|
||||||
);
|
);
|
||||||
if old_message.signed_availability == message.signed_availability {
|
if old_message.signed_availability.as_unchecked() == &bitfield {
|
||||||
modify_reputation(ctx, origin, BENEFIT_VALID_MESSAGE).await;
|
modify_reputation(ctx, origin, BENEFIT_VALID_MESSAGE).await;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if message
|
let signed_availability = match bitfield.try_into_checked(&signing_context, &validator) {
|
||||||
.signed_availability
|
Err(_) => {
|
||||||
.check_signature(&signing_context, &validator)
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
modify_reputation(ctx, origin, COST_SIGNATURE_INVALID).await;
|
modify_reputation(ctx, origin, COST_SIGNATURE_INVALID).await;
|
||||||
return;
|
return;
|
||||||
}
|
},
|
||||||
|
Ok(bitfield) => bitfield,
|
||||||
|
};
|
||||||
|
|
||||||
|
let message = BitfieldGossipMessage {
|
||||||
|
relay_parent,
|
||||||
|
signed_availability,
|
||||||
|
};
|
||||||
|
|
||||||
metrics.on_bitfield_received();
|
metrics.on_bitfield_received();
|
||||||
one_per_validator.insert(validator.clone(), message.clone());
|
one_per_validator.insert(validator.clone(), message.clone());
|
||||||
@@ -544,23 +555,8 @@ where
|
|||||||
);
|
);
|
||||||
handle_our_view_change(state, view);
|
handle_our_view_change(state, view);
|
||||||
}
|
}
|
||||||
NetworkBridgeEvent::PeerMessage(remote, message) => {
|
NetworkBridgeEvent::PeerMessage(remote, message) =>
|
||||||
match message {
|
process_incoming_peer_message(ctx, state, metrics, remote, message).await,
|
||||||
protocol_v1::BitfieldDistributionMessage::Bitfield(relay_parent, bitfield) => {
|
|
||||||
tracing::trace!(
|
|
||||||
target: LOG_TARGET,
|
|
||||||
peer_id = %remote,
|
|
||||||
?relay_parent,
|
|
||||||
"received bitfield gossip from peer"
|
|
||||||
);
|
|
||||||
let gossiped_bitfield = BitfieldGossipMessage {
|
|
||||||
relay_parent,
|
|
||||||
signed_availability: bitfield,
|
|
||||||
};
|
|
||||||
process_incoming_peer_message(ctx, state, metrics, remote, gossiped_bitfield).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,36 +14,36 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet, hash_map::Entry};
|
||||||
|
|
||||||
use super::{LOG_TARGET, Result};
|
use futures::{FutureExt, channel::oneshot, channel::mpsc};
|
||||||
|
|
||||||
use futures::{select, FutureExt, channel::oneshot};
|
|
||||||
use sp_core::Pair;
|
use sp_core::Pair;
|
||||||
|
|
||||||
use polkadot_primitives::v1::{
|
use polkadot_primitives::v1::{AuthorityDiscoveryId, CandidateHash, CandidateReceipt, CollatorPair, CoreIndex, CoreState, GroupIndex, Hash, Id as ParaId};
|
||||||
CandidateHash, CandidateReceipt, CollatorPair, CoreIndex, CoreState, Hash,
|
|
||||||
Id as ParaId, ValidatorId
|
|
||||||
};
|
|
||||||
use polkadot_subsystem::{
|
use polkadot_subsystem::{
|
||||||
jaeger, PerLeafSpan,
|
FromOverseer, OverseerSignal, PerLeafSpan, SubsystemContext, jaeger,
|
||||||
FromOverseer, OverseerSignal, SubsystemContext,
|
messages::{
|
||||||
messages::{AllMessages, CollatorProtocolMessage, NetworkBridgeMessage, NetworkBridgeEvent},
|
AllMessages, CollatorProtocolMessage, NetworkBridgeEvent, NetworkBridgeMessage,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use polkadot_node_network_protocol::{
|
use polkadot_node_network_protocol::{
|
||||||
OurView, PeerId, View, peer_set::PeerSet,
|
OurView, PeerId, View, peer_set::PeerSet,
|
||||||
request_response::{IncomingRequest, v1::{CollationFetchingRequest, CollationFetchingResponse}},
|
request_response::{
|
||||||
v1 as protocol_v1, UnifiedReputationChange as Rep,
|
IncomingRequest,
|
||||||
|
v1::{CollationFetchingRequest, CollationFetchingResponse},
|
||||||
|
},
|
||||||
|
v1 as protocol_v1,
|
||||||
|
UnifiedReputationChange as Rep,
|
||||||
};
|
};
|
||||||
use polkadot_node_subsystem_util::{
|
use polkadot_node_subsystem_util::{
|
||||||
validator_discovery,
|
|
||||||
request_validators,
|
|
||||||
request_validator_groups,
|
|
||||||
request_availability_cores,
|
|
||||||
metrics::{self, prometheus},
|
metrics::{self, prometheus},
|
||||||
|
runtime::{RuntimeInfo, get_availability_cores, get_group_rotation_info}
|
||||||
};
|
};
|
||||||
use polkadot_node_primitives::{SignedFullStatement, Statement, PoV};
|
use polkadot_node_primitives::{SignedFullStatement, Statement, PoV};
|
||||||
|
|
||||||
|
use crate::error::{Fatal, NonFatal, log_error};
|
||||||
|
use super::{LOG_TARGET, Result};
|
||||||
|
|
||||||
const COST_UNEXPECTED_MESSAGE: Rep = Rep::CostMinor("An unexpected message");
|
const COST_UNEXPECTED_MESSAGE: Rep = Rep::CostMinor("An unexpected message");
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
@@ -62,11 +62,6 @@ impl Metrics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provide a timer for handling `ConnectionRequest` which observes on drop.
|
|
||||||
fn time_handle_connection_request(&self) -> Option<prometheus::prometheus::HistogramTimer> {
|
|
||||||
self.0.as_ref().map(|metrics| metrics.handle_connection_request.start_timer())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Provide a timer for `process_msg` which observes on drop.
|
/// Provide a timer for `process_msg` which observes on drop.
|
||||||
fn time_process_msg(&self) -> Option<prometheus::prometheus::HistogramTimer> {
|
fn time_process_msg(&self) -> Option<prometheus::prometheus::HistogramTimer> {
|
||||||
self.0.as_ref().map(|metrics| metrics.process_msg.start_timer())
|
self.0.as_ref().map(|metrics| metrics.process_msg.start_timer())
|
||||||
@@ -77,7 +72,6 @@ impl Metrics {
|
|||||||
struct MetricsInner {
|
struct MetricsInner {
|
||||||
advertisements_made: prometheus::Counter<prometheus::U64>,
|
advertisements_made: prometheus::Counter<prometheus::U64>,
|
||||||
collations_sent: prometheus::Counter<prometheus::U64>,
|
collations_sent: prometheus::Counter<prometheus::U64>,
|
||||||
handle_connection_request: prometheus::Histogram,
|
|
||||||
process_msg: prometheus::Histogram,
|
process_msg: prometheus::Histogram,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,15 +94,6 @@ impl metrics::Metrics for Metrics {
|
|||||||
)?,
|
)?,
|
||||||
registry,
|
registry,
|
||||||
)?,
|
)?,
|
||||||
handle_connection_request: prometheus::register(
|
|
||||||
prometheus::Histogram::with_opts(
|
|
||||||
prometheus::HistogramOpts::new(
|
|
||||||
"parachain_collator_protocol_collator_handle_connection_request",
|
|
||||||
"Time spent within `collator_protocol_collator::handle_connection_request`",
|
|
||||||
)
|
|
||||||
)?,
|
|
||||||
registry,
|
|
||||||
)?,
|
|
||||||
process_msg: prometheus::register(
|
process_msg: prometheus::register(
|
||||||
prometheus::Histogram::with_opts(
|
prometheus::Histogram::with_opts(
|
||||||
prometheus::HistogramOpts::new(
|
prometheus::HistogramOpts::new(
|
||||||
@@ -129,50 +114,36 @@ impl metrics::Metrics for Metrics {
|
|||||||
/// This structure is responsible for keeping track of which validators belong to a certain group for a para. It also
|
/// This structure is responsible for keeping track of which validators belong to a certain group for a para. It also
|
||||||
/// stores a mapping from [`PeerId`] to [`ValidatorId`] as we learn about it over the lifetime of this object. Besides
|
/// stores a mapping from [`PeerId`] to [`ValidatorId`] as we learn about it over the lifetime of this object. Besides
|
||||||
/// that it also keeps track to which validators we advertised our collation.
|
/// that it also keeps track to which validators we advertised our collation.
|
||||||
|
#[derive(Debug)]
|
||||||
struct ValidatorGroup {
|
struct ValidatorGroup {
|
||||||
/// All [`ValidatorId`]'s that are assigned to us in this group.
|
/// All [`AuthorityDiscoveryId`]'s that are assigned to us in this group.
|
||||||
validator_ids: HashSet<ValidatorId>,
|
discovery_ids: HashSet<AuthorityDiscoveryId>,
|
||||||
/// The mapping from [`PeerId`] to [`ValidatorId`]. This is filled over time as we learn the [`PeerId`]'s from the
|
|
||||||
/// authority discovery. It is not ensured that this will contain *all* validators of this group.
|
|
||||||
peer_ids: HashMap<PeerId, ValidatorId>,
|
|
||||||
/// All [`ValidatorId`]'s of the current group to that we advertised our collation.
|
/// All [`ValidatorId`]'s of the current group to that we advertised our collation.
|
||||||
advertised_to: HashSet<ValidatorId>,
|
advertised_to: HashSet<AuthorityDiscoveryId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ValidatorGroup {
|
impl ValidatorGroup {
|
||||||
/// Returns `true` if we should advertise our collation to the given peer.
|
/// Returns `true` if we should advertise our collation to the given peer.
|
||||||
fn should_advertise_to(&self, peer: &PeerId) -> bool {
|
fn should_advertise_to(&self, peer_ids: &HashMap<PeerId, AuthorityDiscoveryId>, peer: &PeerId)
|
||||||
match self.peer_ids.get(peer) {
|
-> bool {
|
||||||
Some(validator_id) => !self.advertised_to.contains(validator_id),
|
match peer_ids.get(peer) {
|
||||||
|
Some(discovery_id) => !self.advertised_to.contains(discovery_id),
|
||||||
None => false,
|
None => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should be called after we advertised our collation to the given `peer` to keep track of it.
|
/// Should be called after we advertised our collation to the given `peer` to keep track of it.
|
||||||
fn advertised_to_peer(&mut self, peer: &PeerId) {
|
fn advertised_to_peer(&mut self, peer_ids: &HashMap<PeerId, AuthorityDiscoveryId>, peer: &PeerId) {
|
||||||
if let Some(validator_id) = self.peer_ids.get(peer) {
|
if let Some(validator_id) = peer_ids.get(peer) {
|
||||||
self.advertised_to.insert(validator_id.clone());
|
self.advertised_to.insert(validator_id.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a [`PeerId`] that belongs to the given [`ValidatorId`].
|
|
||||||
///
|
|
||||||
/// This returns `true` if the given validator belongs to this group and we could insert its [`PeerId`].
|
|
||||||
fn add_peer_id_for_validator(&mut self, peer_id: &PeerId, validator_id: &ValidatorId) -> bool {
|
|
||||||
if !self.validator_ids.contains(validator_id) {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
self.peer_ids.insert(peer_id.clone(), validator_id.clone());
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<HashSet<ValidatorId>> for ValidatorGroup {
|
impl From<HashSet<AuthorityDiscoveryId>> for ValidatorGroup {
|
||||||
fn from(validator_ids: HashSet<ValidatorId>) -> Self {
|
fn from(discovery_ids: HashSet<AuthorityDiscoveryId>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
validator_ids,
|
discovery_ids,
|
||||||
peer_ids: HashMap::new(),
|
|
||||||
advertised_to: HashSet::new(),
|
advertised_to: HashSet::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -243,8 +214,11 @@ struct State {
|
|||||||
/// Our validator groups per active leaf.
|
/// Our validator groups per active leaf.
|
||||||
our_validators_groups: HashMap<Hash, ValidatorGroup>,
|
our_validators_groups: HashMap<Hash, ValidatorGroup>,
|
||||||
|
|
||||||
/// The connection requests to validators per relay parent.
|
/// The mapping from [`PeerId`] to [`ValidatorId`]. This is filled over time as we learn the [`PeerId`]'s by `PeerConnected` events.
|
||||||
connection_requests: validator_discovery::ConnectionRequests,
|
peer_ids: HashMap<PeerId, AuthorityDiscoveryId>,
|
||||||
|
|
||||||
|
/// The connection handles to validators per group we are interested in.
|
||||||
|
connection_handles: HashMap<GroupIndex, mpsc::Receiver<(AuthorityDiscoveryId, PeerId)>>,
|
||||||
|
|
||||||
/// Metrics.
|
/// Metrics.
|
||||||
metrics: Metrics,
|
metrics: Metrics,
|
||||||
@@ -265,13 +239,18 @@ impl State {
|
|||||||
collations: Default::default(),
|
collations: Default::default(),
|
||||||
collation_result_senders: Default::default(),
|
collation_result_senders: Default::default(),
|
||||||
our_validators_groups: Default::default(),
|
our_validators_groups: Default::default(),
|
||||||
connection_requests: Default::default(),
|
peer_ids: Default::default(),
|
||||||
|
connection_handles: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the given `peer` is interested in the leaf that is represented by `relay_parent`.
|
/// Get all peers which have the given relay parent in their view.
|
||||||
fn peer_interested_in_leaf(&self, peer: &PeerId, relay_parent: &Hash) -> bool {
|
fn peers_interested_in_leaf(&self, relay_parent: &Hash) -> Vec<PeerId> {
|
||||||
self.peer_views.get(peer).map(|v| v.contains(relay_parent)).unwrap_or(false)
|
self.peer_views
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, v)| v.contains(relay_parent))
|
||||||
|
.map(|(peer, _)| *peer)
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,9 +262,10 @@ impl State {
|
|||||||
/// or the relay-parent isn't in the active-leaves set, we ignore the message
|
/// or the relay-parent isn't in the active-leaves set, we ignore the message
|
||||||
/// as it must be invalid in that case - although this indicates a logic error
|
/// as it must be invalid in that case - although this indicates a logic error
|
||||||
/// elsewhere in the node.
|
/// elsewhere in the node.
|
||||||
#[tracing::instrument(level = "trace", skip(ctx, state, pov), fields(subsystem = LOG_TARGET))]
|
#[tracing::instrument(level = "trace", skip(ctx, runtime, state, pov), fields(subsystem = LOG_TARGET))]
|
||||||
async fn distribute_collation(
|
async fn distribute_collation(
|
||||||
ctx: &mut impl SubsystemContext<Message = CollatorProtocolMessage>,
|
ctx: &mut impl SubsystemContext,
|
||||||
|
runtime: &mut RuntimeInfo,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
id: ParaId,
|
id: ParaId,
|
||||||
receipt: CandidateReceipt,
|
receipt: CandidateReceipt,
|
||||||
@@ -327,9 +307,10 @@ async fn distribute_collation(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Determine the group on that core and the next group on that core.
|
// Determine the group on that core and the next group on that core.
|
||||||
let (current_validators, next_validators) = determine_our_validators(ctx, our_core, num_cores, relay_parent).await?;
|
let (current_validators, next_validators) =
|
||||||
|
determine_our_validators(ctx, runtime, our_core, num_cores, relay_parent,).await?;
|
||||||
|
|
||||||
if current_validators.is_empty() && next_validators.is_empty() {
|
if current_validators.validators.is_empty() && next_validators.validators.is_empty() {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
core = ?our_core,
|
core = ?our_core,
|
||||||
@@ -350,16 +331,26 @@ async fn distribute_collation(
|
|||||||
?next_validators,
|
?next_validators,
|
||||||
"Accepted collation, connecting to validators."
|
"Accepted collation, connecting to validators."
|
||||||
);
|
);
|
||||||
// Issue a discovery request for the validators of the current group and the next group.
|
|
||||||
|
// Drop obsolete connections:
|
||||||
|
let new_groups: HashSet<_> = vec![current_validators.group, next_validators.group].into_iter().collect();
|
||||||
|
state.connection_handles.retain(|k, _| new_groups.contains(k));
|
||||||
|
|
||||||
|
let validator_group: HashSet<_> = current_validators.validators.iter().map(Clone::clone).collect();
|
||||||
|
|
||||||
|
// Issue a discovery request for the validators of the current group and the next group:
|
||||||
connect_to_validators(
|
connect_to_validators(
|
||||||
ctx,
|
ctx,
|
||||||
relay_parent,
|
|
||||||
id,
|
|
||||||
state,
|
state,
|
||||||
current_validators.union(&next_validators).cloned().collect(),
|
current_validators,
|
||||||
).await?;
|
).await;
|
||||||
|
connect_to_validators(
|
||||||
|
ctx,
|
||||||
|
state,
|
||||||
|
next_validators,
|
||||||
|
).await;
|
||||||
|
|
||||||
state.our_validators_groups.insert(relay_parent, current_validators.into());
|
state.our_validators_groups.insert(relay_parent, validator_group.into());
|
||||||
|
|
||||||
if let Some(result_sender) = result_sender {
|
if let Some(result_sender) = result_sender {
|
||||||
state.collation_result_senders.insert(receipt.hash(), result_sender);
|
state.collation_result_senders.insert(receipt.hash(), result_sender);
|
||||||
@@ -367,6 +358,11 @@ async fn distribute_collation(
|
|||||||
|
|
||||||
state.collations.insert(relay_parent, Collation { receipt, pov, status: CollationStatus::Created });
|
state.collations.insert(relay_parent, Collation { receipt, pov, status: CollationStatus::Created });
|
||||||
|
|
||||||
|
// Make sure already connected peers get collations:
|
||||||
|
for peer_id in state.peers_interested_in_leaf(&relay_parent) {
|
||||||
|
advertise_collation(ctx, state, relay_parent, peer_id).await;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,11 +370,11 @@ async fn distribute_collation(
|
|||||||
/// and the total number of cores.
|
/// and the total number of cores.
|
||||||
#[tracing::instrument(level = "trace", skip(ctx), fields(subsystem = LOG_TARGET))]
|
#[tracing::instrument(level = "trace", skip(ctx), fields(subsystem = LOG_TARGET))]
|
||||||
async fn determine_core(
|
async fn determine_core(
|
||||||
ctx: &mut impl SubsystemContext<Message = CollatorProtocolMessage>,
|
ctx: &mut impl SubsystemContext,
|
||||||
para_id: ParaId,
|
para_id: ParaId,
|
||||||
relay_parent: Hash,
|
relay_parent: Hash,
|
||||||
) -> Result<Option<(CoreIndex, usize)>> {
|
) -> Result<Option<(CoreIndex, usize)>> {
|
||||||
let cores = request_availability_cores(relay_parent, ctx.sender()).await.await??;
|
let cores = get_availability_cores(ctx, relay_parent).await?;
|
||||||
|
|
||||||
for (idx, core) in cores.iter().enumerate() {
|
for (idx, core) in cores.iter().enumerate() {
|
||||||
if let CoreState::Scheduled(occupied) = core {
|
if let CoreState::Scheduled(occupied) = core {
|
||||||
@@ -387,35 +383,54 @@ async fn determine_core(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Validators of a particular group index.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct GroupValidators {
|
||||||
|
/// The group those validators belong to.
|
||||||
|
group: GroupIndex,
|
||||||
|
/// The validators of above group (their discovery keys).
|
||||||
|
validators: Vec<AuthorityDiscoveryId>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Figure out current and next group of validators assigned to the para being collated on.
|
/// Figure out current and next group of validators assigned to the para being collated on.
|
||||||
///
|
///
|
||||||
/// Returns [`ValidatorId`]'s of current and next group as determined based on the `relay_parent`.
|
/// Returns [`ValidatorId`]'s of current and next group as determined based on the `relay_parent`.
|
||||||
#[tracing::instrument(level = "trace", skip(ctx), fields(subsystem = LOG_TARGET))]
|
#[tracing::instrument(level = "trace", skip(ctx, runtime), fields(subsystem = LOG_TARGET))]
|
||||||
async fn determine_our_validators(
|
async fn determine_our_validators(
|
||||||
ctx: &mut impl SubsystemContext<Message = CollatorProtocolMessage>,
|
ctx: &mut impl SubsystemContext,
|
||||||
|
runtime: &mut RuntimeInfo,
|
||||||
core_index: CoreIndex,
|
core_index: CoreIndex,
|
||||||
cores: usize,
|
cores: usize,
|
||||||
relay_parent: Hash,
|
relay_parent: Hash,
|
||||||
) -> Result<(HashSet<ValidatorId>, HashSet<ValidatorId>)> {
|
) -> Result<(GroupValidators, GroupValidators)> {
|
||||||
let groups = request_validator_groups(relay_parent, ctx.sender()).await;
|
let info = &runtime.get_session_info(ctx, relay_parent).await?.session_info;
|
||||||
|
tracing::debug!(target: LOG_TARGET, ?info, "Received info");
|
||||||
|
let groups = &info.validator_groups;
|
||||||
|
let rotation_info = get_group_rotation_info(ctx, relay_parent).await?;
|
||||||
|
|
||||||
let groups = groups.await??;
|
let current_group_index = rotation_info.group_for_core(core_index, cores);
|
||||||
|
let current_validators = groups.get(current_group_index.0 as usize).map(|v| v.as_slice()).unwrap_or_default();
|
||||||
|
|
||||||
let current_group_index = groups.1.group_for_core(core_index, cores);
|
let next_group_idx = (current_group_index.0 as usize + 1) % groups.len();
|
||||||
let current_validators = groups.0.get(current_group_index.0 as usize).map(|v| v.as_slice()).unwrap_or_default();
|
let next_validators = groups.get(next_group_idx).map(|v| v.as_slice()).unwrap_or_default();
|
||||||
|
|
||||||
let next_group_idx = (current_group_index.0 as usize + 1) % groups.0.len();
|
let validators = &info.discovery_keys;
|
||||||
let next_validators = groups.0.get(next_group_idx).map(|v| v.as_slice()).unwrap_or_default();
|
|
||||||
|
|
||||||
let validators = request_validators(relay_parent, ctx.sender()).await.await??;
|
|
||||||
|
|
||||||
let current_validators = current_validators.iter().map(|i| validators[i.0 as usize].clone()).collect();
|
let current_validators = current_validators.iter().map(|i| validators[i.0 as usize].clone()).collect();
|
||||||
let next_validators = next_validators.iter().map(|i| validators[i.0 as usize].clone()).collect();
|
let next_validators = next_validators.iter().map(|i| validators[i.0 as usize].clone()).collect();
|
||||||
|
|
||||||
|
let current_validators = GroupValidators {
|
||||||
|
group: current_group_index,
|
||||||
|
validators: current_validators,
|
||||||
|
};
|
||||||
|
let next_validators = GroupValidators {
|
||||||
|
group: GroupIndex(next_group_idx as u32),
|
||||||
|
validators: next_validators,
|
||||||
|
};
|
||||||
|
|
||||||
Ok((current_validators, next_validators))
|
Ok((current_validators, next_validators))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,22 +463,20 @@ async fn declare(
|
|||||||
/// revoke the previous connection request.
|
/// revoke the previous connection request.
|
||||||
#[tracing::instrument(level = "trace", skip(ctx, state), fields(subsystem = LOG_TARGET))]
|
#[tracing::instrument(level = "trace", skip(ctx, state), fields(subsystem = LOG_TARGET))]
|
||||||
async fn connect_to_validators(
|
async fn connect_to_validators(
|
||||||
ctx: &mut impl SubsystemContext<Message = CollatorProtocolMessage>,
|
ctx: &mut impl SubsystemContext,
|
||||||
relay_parent: Hash,
|
|
||||||
para_id: ParaId,
|
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
validators: Vec<ValidatorId>,
|
group: GroupValidators,
|
||||||
) -> Result<()> {
|
) {
|
||||||
let request = validator_discovery::connect_to_validators(
|
match state.connection_handles.entry(group.group) {
|
||||||
ctx,
|
Entry::Occupied(_) => {}
|
||||||
relay_parent,
|
Entry::Vacant(vacant) => {
|
||||||
validators,
|
let (tx, rx) = mpsc::channel(0);
|
||||||
PeerSet::Collation,
|
ctx.send_message(AllMessages::NetworkBridge(NetworkBridgeMessage::ConnectToValidators {
|
||||||
).await?;
|
validator_ids: group.validators, peer_set: PeerSet::Collation, connected: tx
|
||||||
|
})).await;
|
||||||
state.connection_requests.put(relay_parent, para_id, request);
|
vacant.insert(rx);
|
||||||
|
}
|
||||||
Ok(())
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Advertise collation to the given `peer`.
|
/// Advertise collation to the given `peer`.
|
||||||
@@ -472,14 +485,14 @@ async fn connect_to_validators(
|
|||||||
/// set as validator for our para at the given `relay_parent`.
|
/// set as validator for our para at the given `relay_parent`.
|
||||||
#[tracing::instrument(level = "trace", skip(ctx, state), fields(subsystem = LOG_TARGET))]
|
#[tracing::instrument(level = "trace", skip(ctx, state), fields(subsystem = LOG_TARGET))]
|
||||||
async fn advertise_collation(
|
async fn advertise_collation(
|
||||||
ctx: &mut impl SubsystemContext<Message = CollatorProtocolMessage>,
|
ctx: &mut impl SubsystemContext,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
relay_parent: Hash,
|
relay_parent: Hash,
|
||||||
peer: PeerId,
|
peer: PeerId,
|
||||||
) {
|
) {
|
||||||
let should_advertise = state.our_validators_groups
|
let should_advertise = state.our_validators_groups
|
||||||
.get(&relay_parent)
|
.get(&relay_parent)
|
||||||
.map(|g| g.should_advertise_to(&peer))
|
.map(|g| g.should_advertise_to(&state.peer_ids, &peer))
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|
||||||
match (state.collations.get_mut(&relay_parent), should_advertise) {
|
match (state.collations.get_mut(&relay_parent), should_advertise) {
|
||||||
@@ -524,16 +537,17 @@ async fn advertise_collation(
|
|||||||
)).await;
|
)).await;
|
||||||
|
|
||||||
if let Some(validators) = state.our_validators_groups.get_mut(&relay_parent) {
|
if let Some(validators) = state.our_validators_groups.get_mut(&relay_parent) {
|
||||||
validators.advertised_to_peer(&peer);
|
validators.advertised_to_peer(&state.peer_ids, &peer);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.metrics.on_advertisment_made();
|
state.metrics.on_advertisment_made();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The main incoming message dispatching switch.
|
/// The main incoming message dispatching switch.
|
||||||
#[tracing::instrument(level = "trace", skip(ctx, state), fields(subsystem = LOG_TARGET))]
|
#[tracing::instrument(level = "trace", skip(ctx, runtime, state), fields(subsystem = LOG_TARGET))]
|
||||||
async fn process_msg(
|
async fn process_msg(
|
||||||
ctx: &mut impl SubsystemContext<Message = CollatorProtocolMessage>,
|
ctx: &mut impl SubsystemContext<Message = CollatorProtocolMessage>,
|
||||||
|
runtime: &mut RuntimeInfo,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
msg: CollatorProtocolMessage,
|
msg: CollatorProtocolMessage,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
@@ -561,7 +575,7 @@ async fn process_msg(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
Some(id) => {
|
Some(id) => {
|
||||||
distribute_collation(ctx, state, id, receipt, pov, result_sender).await?;
|
distribute_collation(ctx, runtime, state, id, receipt, pov, result_sender).await?;
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
@@ -590,7 +604,7 @@ async fn process_msg(
|
|||||||
"NoteGoodCollation message is not expected on the collator side of the protocol",
|
"NoteGoodCollation message is not expected on the collator side of the protocol",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
NotifyCollationSeconded(_, _) => {
|
NotifyCollationSeconded(_, _, _) => {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
"NotifyCollationSeconded message is not expected on the collator side of the protocol",
|
"NotifyCollationSeconded message is not expected on the collator side of the protocol",
|
||||||
@@ -599,6 +613,7 @@ async fn process_msg(
|
|||||||
NetworkBridgeUpdateV1(event) => {
|
NetworkBridgeUpdateV1(event) => {
|
||||||
if let Err(e) = handle_network_msg(
|
if let Err(e) = handle_network_msg(
|
||||||
ctx,
|
ctx,
|
||||||
|
runtime,
|
||||||
state,
|
state,
|
||||||
event,
|
event,
|
||||||
).await {
|
).await {
|
||||||
@@ -670,9 +685,10 @@ async fn send_collation(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A networking messages switch.
|
/// A networking messages switch.
|
||||||
#[tracing::instrument(level = "trace", skip(ctx, state), fields(subsystem = LOG_TARGET))]
|
#[tracing::instrument(level = "trace", skip(ctx, runtime, state), fields(subsystem = LOG_TARGET))]
|
||||||
async fn handle_incoming_peer_message(
|
async fn handle_incoming_peer_message(
|
||||||
ctx: &mut impl SubsystemContext,
|
ctx: &mut impl SubsystemContext,
|
||||||
|
runtime: &mut RuntimeInfo,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
origin: PeerId,
|
origin: PeerId,
|
||||||
msg: protocol_v1::CollatorProtocolMessage,
|
msg: protocol_v1::CollatorProtocolMessage,
|
||||||
@@ -708,15 +724,23 @@ async fn handle_incoming_peer_message(
|
|||||||
NetworkBridgeMessage::DisconnectPeer(origin, PeerSet::Collation).into()
|
NetworkBridgeMessage::DisconnectPeer(origin, PeerSet::Collation).into()
|
||||||
).await;
|
).await;
|
||||||
}
|
}
|
||||||
CollationSeconded(statement) => {
|
CollationSeconded(relay_parent, statement) => {
|
||||||
if !matches!(statement.payload(), Statement::Seconded(_)) {
|
if !matches!(statement.unchecked_payload(), Statement::Seconded(_)) {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
?statement,
|
?statement,
|
||||||
?origin,
|
?origin,
|
||||||
"Collation seconded message received with none-seconded statement.",
|
"Collation seconded message received with none-seconded statement.",
|
||||||
);
|
);
|
||||||
} else if let Some(sender) = state.collation_result_senders.remove(&statement.payload().candidate_hash()) {
|
} else {
|
||||||
|
let statement = runtime.check_signature(ctx, relay_parent, statement)
|
||||||
|
.await?
|
||||||
|
.map_err(NonFatal::InvalidStatementSignature)?;
|
||||||
|
|
||||||
|
let removed = state.collation_result_senders
|
||||||
|
.remove(&statement.payload().candidate_hash());
|
||||||
|
|
||||||
|
if let Some(sender) = removed {
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
?statement,
|
?statement,
|
||||||
@@ -727,6 +751,7 @@ async fn handle_incoming_peer_message(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -750,48 +775,18 @@ async fn handle_peer_view_change(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A validator is connected.
|
|
||||||
///
|
|
||||||
/// `Declare` that we are a collator with a given `CollatorId`.
|
|
||||||
#[tracing::instrument(level = "trace", skip(ctx, state), fields(subsystem = LOG_TARGET))]
|
|
||||||
async fn handle_validator_connected(
|
|
||||||
ctx: &mut impl SubsystemContext<Message = CollatorProtocolMessage>,
|
|
||||||
state: &mut State,
|
|
||||||
peer_id: PeerId,
|
|
||||||
validator_id: ValidatorId,
|
|
||||||
relay_parent: Hash,
|
|
||||||
) {
|
|
||||||
tracing::trace!(
|
|
||||||
target: LOG_TARGET,
|
|
||||||
?validator_id,
|
|
||||||
"Connected to requested validator"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Store the PeerId and find out if we should advertise to this peer.
|
|
||||||
//
|
|
||||||
// If this peer does not belong to the para validators, we also don't need to try to advertise our collation.
|
|
||||||
let advertise = if let Some(validators) = state.our_validators_groups.get_mut(&relay_parent) {
|
|
||||||
validators.add_peer_id_for_validator(&peer_id, &validator_id)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
if advertise && state.peer_interested_in_leaf(&peer_id, &relay_parent) {
|
|
||||||
advertise_collation(ctx, state, relay_parent, peer_id).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Bridge messages switch.
|
/// Bridge messages switch.
|
||||||
#[tracing::instrument(level = "trace", skip(ctx, state), fields(subsystem = LOG_TARGET))]
|
#[tracing::instrument(level = "trace", skip(ctx, runtime, state), fields(subsystem = LOG_TARGET))]
|
||||||
async fn handle_network_msg(
|
async fn handle_network_msg(
|
||||||
ctx: &mut impl SubsystemContext<Message = CollatorProtocolMessage>,
|
ctx: &mut impl SubsystemContext<Message = CollatorProtocolMessage>,
|
||||||
|
runtime: &mut RuntimeInfo,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
bridge_message: NetworkBridgeEvent<protocol_v1::CollatorProtocolMessage>,
|
bridge_message: NetworkBridgeEvent<protocol_v1::CollatorProtocolMessage>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
use NetworkBridgeEvent::*;
|
use NetworkBridgeEvent::*;
|
||||||
|
|
||||||
match bridge_message {
|
match bridge_message {
|
||||||
PeerConnected(peer_id, observed_role, _) => {
|
PeerConnected(peer_id, observed_role, maybe_authority) => {
|
||||||
// If it is possible that a disconnected validator would attempt a reconnect
|
// If it is possible that a disconnected validator would attempt a reconnect
|
||||||
// it should be handled here.
|
// it should be handled here.
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
@@ -800,9 +795,17 @@ async fn handle_network_msg(
|
|||||||
?observed_role,
|
?observed_role,
|
||||||
"Peer connected",
|
"Peer connected",
|
||||||
);
|
);
|
||||||
|
if let Some(authority) = maybe_authority {
|
||||||
|
tracing::trace!(
|
||||||
|
target: LOG_TARGET,
|
||||||
|
?authority,
|
||||||
|
?peer_id,
|
||||||
|
"Connected to requested validator"
|
||||||
|
);
|
||||||
|
state.peer_ids.insert(peer_id, authority);
|
||||||
|
|
||||||
// Always declare to every peer. We should be connecting only to validators.
|
declare(ctx, state, peer_id).await;
|
||||||
declare(ctx, state, peer_id.clone()).await;
|
}
|
||||||
}
|
}
|
||||||
PeerViewChange(peer_id, view) => {
|
PeerViewChange(peer_id, view) => {
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
@@ -820,6 +823,7 @@ async fn handle_network_msg(
|
|||||||
"Peer disconnected",
|
"Peer disconnected",
|
||||||
);
|
);
|
||||||
state.peer_views.remove(&peer_id);
|
state.peer_views.remove(&peer_id);
|
||||||
|
state.peer_ids.remove(&peer_id);
|
||||||
}
|
}
|
||||||
OurViewChange(view) => {
|
OurViewChange(view) => {
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
@@ -830,7 +834,7 @@ async fn handle_network_msg(
|
|||||||
handle_our_view_change(state, view).await?;
|
handle_our_view_change(state, view).await?;
|
||||||
}
|
}
|
||||||
PeerMessage(remote, msg) => {
|
PeerMessage(remote, msg) => {
|
||||||
handle_incoming_peer_message(ctx, state, remote, msg).await?;
|
handle_incoming_peer_message(ctx, runtime, state, remote, msg).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -871,7 +875,6 @@ async fn handle_our_view_change(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.our_validators_groups.remove(removed);
|
state.our_validators_groups.remove(removed);
|
||||||
state.connection_requests.remove_all(removed);
|
|
||||||
state.span_per_relay_parent.remove(removed);
|
state.span_per_relay_parent.remove(removed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -892,32 +895,22 @@ pub(crate) async fn run(
|
|||||||
use OverseerSignal::*;
|
use OverseerSignal::*;
|
||||||
|
|
||||||
let mut state = State::new(local_peer_id, collator_pair, metrics);
|
let mut state = State::new(local_peer_id, collator_pair, metrics);
|
||||||
|
let mut runtime = RuntimeInfo::new(None);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
select! {
|
let msg = ctx.recv().fuse().await.map_err(Fatal::SubsystemReceive)?;
|
||||||
res = state.connection_requests.next().fuse() => {
|
match msg {
|
||||||
let _timer = state.metrics.time_handle_connection_request();
|
|
||||||
|
|
||||||
handle_validator_connected(
|
|
||||||
&mut ctx,
|
|
||||||
&mut state,
|
|
||||||
res.peer_id,
|
|
||||||
res.validator_id,
|
|
||||||
res.relay_parent,
|
|
||||||
).await;
|
|
||||||
},
|
|
||||||
msg = ctx.recv().fuse() => match msg? {
|
|
||||||
Communication { msg } => {
|
Communication { msg } => {
|
||||||
if let Err(e) = process_msg(&mut ctx, &mut state, msg).await {
|
log_error(
|
||||||
tracing::warn!(target: LOG_TARGET, err = ?e, "Failed to process message");
|
process_msg(&mut ctx, &mut runtime, &mut state, msg).await,
|
||||||
}
|
"Failed to process message"
|
||||||
|
)?;
|
||||||
},
|
},
|
||||||
Signal(ActiveLeaves(_update)) => {}
|
Signal(ActiveLeaves(_update)) => {}
|
||||||
Signal(BlockFinalized(..)) => {}
|
Signal(BlockFinalized(..)) => {}
|
||||||
Signal(Conclude) => return Ok(()),
|
Signal(Conclude) => return Ok(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -939,10 +932,7 @@ mod tests {
|
|||||||
request_response::request::IncomingRequest,
|
request_response::request::IncomingRequest,
|
||||||
};
|
};
|
||||||
use polkadot_node_subsystem_util::TimeoutExt;
|
use polkadot_node_subsystem_util::TimeoutExt;
|
||||||
use polkadot_primitives::v1::{
|
use polkadot_primitives::v1::{AuthorityDiscoveryId, CandidateDescriptor, CollatorPair, GroupRotationInfo, ScheduledCore, SessionIndex, SessionInfo, ValidatorId, ValidatorIndex};
|
||||||
AuthorityDiscoveryId, CandidateDescriptor, CollatorPair, GroupRotationInfo,
|
|
||||||
ScheduledCore, SessionIndex, SessionInfo, ValidatorIndex,
|
|
||||||
};
|
|
||||||
use polkadot_node_primitives::BlockData;
|
use polkadot_node_primitives::BlockData;
|
||||||
use polkadot_subsystem::{
|
use polkadot_subsystem::{
|
||||||
jaeger,
|
jaeger,
|
||||||
@@ -977,10 +967,9 @@ mod tests {
|
|||||||
struct TestState {
|
struct TestState {
|
||||||
para_id: ParaId,
|
para_id: ParaId,
|
||||||
validators: Vec<Sr25519Keyring>,
|
validators: Vec<Sr25519Keyring>,
|
||||||
validator_public: Vec<ValidatorId>,
|
session_info: SessionInfo,
|
||||||
validator_authority_id: Vec<AuthorityDiscoveryId>,
|
group_rotation_info: GroupRotationInfo,
|
||||||
validator_peer_id: Vec<PeerId>,
|
validator_peer_id: Vec<PeerId>,
|
||||||
validator_groups: (Vec<Vec<ValidatorIndex>>, GroupRotationInfo),
|
|
||||||
relay_parent: Hash,
|
relay_parent: Hash,
|
||||||
availability_core: CoreState,
|
availability_core: CoreState,
|
||||||
local_peer_id: PeerId,
|
local_peer_id: PeerId,
|
||||||
@@ -1009,10 +998,10 @@ mod tests {
|
|||||||
];
|
];
|
||||||
|
|
||||||
let validator_public = validator_pubkeys(&validators);
|
let validator_public = validator_pubkeys(&validators);
|
||||||
let validator_authority_id = validator_authority_id(&validators);
|
let discovery_keys = validator_authority_id(&validators);
|
||||||
|
|
||||||
let validator_peer_id = std::iter::repeat_with(|| PeerId::random())
|
let validator_peer_id = std::iter::repeat_with(|| PeerId::random())
|
||||||
.take(validator_public.len())
|
.take(discovery_keys.len())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let validator_groups = vec![vec![2, 0, 4], vec![3, 2, 4]]
|
let validator_groups = vec![vec![2, 0, 4], vec![3, 2, 4]]
|
||||||
@@ -1022,7 +1011,6 @@ mod tests {
|
|||||||
group_rotation_frequency: 100,
|
group_rotation_frequency: 100,
|
||||||
now: 1,
|
now: 1,
|
||||||
};
|
};
|
||||||
let validator_groups = (validator_groups, group_rotation_info);
|
|
||||||
|
|
||||||
let availability_core = CoreState::Scheduled(ScheduledCore {
|
let availability_core = CoreState::Scheduled(ScheduledCore {
|
||||||
para_id,
|
para_id,
|
||||||
@@ -1037,10 +1025,14 @@ mod tests {
|
|||||||
Self {
|
Self {
|
||||||
para_id,
|
para_id,
|
||||||
validators,
|
validators,
|
||||||
validator_public,
|
session_info: SessionInfo {
|
||||||
validator_authority_id,
|
validators: validator_public,
|
||||||
validator_peer_id,
|
discovery_keys,
|
||||||
validator_groups,
|
validator_groups,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
group_rotation_info,
|
||||||
|
validator_peer_id,
|
||||||
relay_parent,
|
relay_parent,
|
||||||
availability_core,
|
availability_core,
|
||||||
local_peer_id,
|
local_peer_id,
|
||||||
@@ -1052,7 +1044,7 @@ mod tests {
|
|||||||
|
|
||||||
impl TestState {
|
impl TestState {
|
||||||
fn current_group_validator_indices(&self) -> &[ValidatorIndex] {
|
fn current_group_validator_indices(&self) -> &[ValidatorIndex] {
|
||||||
&self.validator_groups.0[0]
|
&self.session_info.validator_groups[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn current_session_index(&self) -> SessionIndex {
|
fn current_session_index(&self) -> SessionIndex {
|
||||||
@@ -1066,25 +1058,7 @@ mod tests {
|
|||||||
fn current_group_validator_authority_ids(&self) -> Vec<AuthorityDiscoveryId> {
|
fn current_group_validator_authority_ids(&self) -> Vec<AuthorityDiscoveryId> {
|
||||||
self.current_group_validator_indices()
|
self.current_group_validator_indices()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|i| self.validator_authority_id[i.0 as usize].clone())
|
.map(|i| self.session_info.discovery_keys[i.0 as usize].clone())
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn current_group_validator_ids(&self) -> Vec<ValidatorId> {
|
|
||||||
self.current_group_validator_indices()
|
|
||||||
.iter()
|
|
||||||
.map(|i| self.validator_public[i.0 as usize].clone())
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_group_validator_indices(&self) -> &[ValidatorIndex] {
|
|
||||||
&self.validator_groups.0[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_group_validator_authority_ids(&self) -> Vec<AuthorityDiscoveryId> {
|
|
||||||
self.next_group_validator_indices()
|
|
||||||
.iter()
|
|
||||||
.map(|i| self.validator_authority_id[i.0 as usize].clone())
|
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1218,7 +1192,7 @@ mod tests {
|
|||||||
/// Result of [`distribute_collation`]
|
/// Result of [`distribute_collation`]
|
||||||
struct DistributeCollation {
|
struct DistributeCollation {
|
||||||
/// Should be used to inform the subsystem about connected validators.
|
/// Should be used to inform the subsystem about connected validators.
|
||||||
connected: mpsc::Sender<(AuthorityDiscoveryId, PeerId)>,
|
connected: Vec<mpsc::Sender<(AuthorityDiscoveryId, PeerId)>>,
|
||||||
candidate: CandidateReceipt,
|
candidate: CandidateReceipt,
|
||||||
pov_block: PoV,
|
pov_block: PoV,
|
||||||
}
|
}
|
||||||
@@ -1227,6 +1201,8 @@ mod tests {
|
|||||||
async fn distribute_collation(
|
async fn distribute_collation(
|
||||||
virtual_overseer: &mut VirtualOverseer,
|
virtual_overseer: &mut VirtualOverseer,
|
||||||
test_state: &TestState,
|
test_state: &TestState,
|
||||||
|
// whether or not the currently active validators are already connected or not.
|
||||||
|
already_connected: bool,
|
||||||
) -> DistributeCollation {
|
) -> DistributeCollation {
|
||||||
// Now we want to distribute a PoVBlock
|
// Now we want to distribute a PoVBlock
|
||||||
let pov_block = PoV {
|
let pov_block = PoV {
|
||||||
@@ -1259,33 +1235,9 @@ mod tests {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Obtain the validator groups
|
// We don't know precisely what is going to come as session info might be cached:
|
||||||
assert_matches!(
|
loop {
|
||||||
overseer_recv(virtual_overseer).await,
|
match overseer_recv(virtual_overseer).await {
|
||||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
||||||
relay_parent,
|
|
||||||
RuntimeApiRequest::ValidatorGroups(tx)
|
|
||||||
)) => {
|
|
||||||
assert_eq!(relay_parent, test_state.relay_parent);
|
|
||||||
tx.send(Ok(test_state.validator_groups.clone())).unwrap();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// obtain the validators per relay parent
|
|
||||||
assert_matches!(
|
|
||||||
overseer_recv(virtual_overseer).await,
|
|
||||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
|
||||||
relay_parent,
|
|
||||||
RuntimeApiRequest::Validators(tx),
|
|
||||||
)) => {
|
|
||||||
assert_eq!(relay_parent, test_state.relay_parent);
|
|
||||||
tx.send(Ok(test_state.validator_public.clone())).unwrap();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// obtain the validator_id to authority_id mapping
|
|
||||||
assert_matches!(
|
|
||||||
overseer_recv(virtual_overseer).await,
|
|
||||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||||
relay_parent,
|
relay_parent,
|
||||||
RuntimeApiRequest::SessionIndexForChild(tx),
|
RuntimeApiRequest::SessionIndexForChild(tx),
|
||||||
@@ -1293,10 +1245,7 @@ mod tests {
|
|||||||
assert_eq!(relay_parent, test_state.relay_parent);
|
assert_eq!(relay_parent, test_state.relay_parent);
|
||||||
tx.send(Ok(test_state.current_session_index())).unwrap();
|
tx.send(Ok(test_state.current_session_index())).unwrap();
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
assert_matches!(
|
|
||||||
overseer_recv(virtual_overseer).await,
|
|
||||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||||
relay_parent,
|
relay_parent,
|
||||||
RuntimeApiRequest::SessionInfo(index, tx),
|
RuntimeApiRequest::SessionInfo(index, tx),
|
||||||
@@ -1304,46 +1253,70 @@ mod tests {
|
|||||||
assert_eq!(relay_parent, test_state.relay_parent);
|
assert_eq!(relay_parent, test_state.relay_parent);
|
||||||
assert_eq!(index, test_state.current_session_index());
|
assert_eq!(index, test_state.current_session_index());
|
||||||
|
|
||||||
let validators = test_state.current_group_validator_ids();
|
tx.send(Ok(Some(test_state.session_info.clone()))).unwrap();
|
||||||
let current_discovery_keys = test_state.current_group_validator_authority_ids();
|
|
||||||
let next_discovery_keys = test_state.next_group_validator_authority_ids();
|
|
||||||
|
|
||||||
let discovery_keys = [¤t_discovery_keys[..], &next_discovery_keys[..]].concat();
|
|
||||||
|
|
||||||
tx.send(Ok(Some(SessionInfo {
|
|
||||||
validators,
|
|
||||||
discovery_keys,
|
|
||||||
..Default::default()
|
|
||||||
}))).unwrap();
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
assert_matches!(
|
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||||
|
relay_parent,
|
||||||
|
RuntimeApiRequest::ValidatorGroups(tx)
|
||||||
|
)) => {
|
||||||
|
assert_eq!(relay_parent, test_state.relay_parent);
|
||||||
|
tx.send(Ok((
|
||||||
|
test_state.session_info.validator_groups.clone(),
|
||||||
|
test_state.group_rotation_info.clone(),
|
||||||
|
))).unwrap();
|
||||||
|
// This call is mandatory - we are done:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
other =>
|
||||||
|
panic!("Unexpected message received: {:?}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let connected = if already_connected {
|
||||||
|
Vec::new()
|
||||||
|
} else {
|
||||||
|
let connected_current = assert_matches!(
|
||||||
overseer_recv(virtual_overseer).await,
|
overseer_recv(virtual_overseer).await,
|
||||||
AllMessages::NetworkBridge(
|
AllMessages::NetworkBridge(
|
||||||
NetworkBridgeMessage::ConnectToValidators {
|
NetworkBridgeMessage::ConnectToValidators {
|
||||||
connected,
|
connected,
|
||||||
..
|
..
|
||||||
}
|
}
|
||||||
) => {
|
) => { connected }
|
||||||
|
);
|
||||||
|
let connected_next = assert_matches!(
|
||||||
|
overseer_recv(virtual_overseer).await,
|
||||||
|
AllMessages::NetworkBridge(
|
||||||
|
NetworkBridgeMessage::ConnectToValidators {
|
||||||
|
connected,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
) => { connected }
|
||||||
|
);
|
||||||
|
vec![connected_current, connected_next]
|
||||||
|
};
|
||||||
|
|
||||||
DistributeCollation {
|
DistributeCollation {
|
||||||
connected,
|
connected,
|
||||||
candidate,
|
candidate,
|
||||||
pov_block,
|
pov_block,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Connect a peer
|
/// Connect a peer
|
||||||
async fn connect_peer(virtual_overseer: &mut VirtualOverseer, peer: PeerId) {
|
async fn connect_peer(
|
||||||
|
virtual_overseer: &mut VirtualOverseer,
|
||||||
|
peer: PeerId,
|
||||||
|
authority_id: Option<AuthorityDiscoveryId>
|
||||||
|
) {
|
||||||
overseer_send(
|
overseer_send(
|
||||||
virtual_overseer,
|
virtual_overseer,
|
||||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(
|
CollatorProtocolMessage::NetworkBridgeUpdateV1(
|
||||||
NetworkBridgeEvent::PeerConnected(
|
NetworkBridgeEvent::PeerConnected(
|
||||||
peer.clone(),
|
peer.clone(),
|
||||||
polkadot_node_network_protocol::ObservedRole::Authority,
|
polkadot_node_network_protocol::ObservedRole::Authority,
|
||||||
None,
|
authority_id,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
).await;
|
).await;
|
||||||
@@ -1446,15 +1419,14 @@ mod tests {
|
|||||||
|
|
||||||
setup_system(&mut virtual_overseer, &test_state).await;
|
setup_system(&mut virtual_overseer, &test_state).await;
|
||||||
|
|
||||||
let DistributeCollation { mut connected, candidate, pov_block } =
|
let DistributeCollation { connected: _connected, candidate, pov_block } =
|
||||||
distribute_collation(&mut virtual_overseer, &test_state).await;
|
distribute_collation(&mut virtual_overseer, &test_state, false).await;
|
||||||
|
|
||||||
for (val, peer) in test_state.current_group_validator_authority_ids()
|
for (val, peer) in test_state.current_group_validator_authority_ids()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(test_state.current_group_validator_peer_ids())
|
.zip(test_state.current_group_validator_peer_ids())
|
||||||
{
|
{
|
||||||
connect_peer(&mut virtual_overseer, peer.clone()).await;
|
connect_peer(&mut virtual_overseer, peer.clone(), Some(val.clone())).await;
|
||||||
connected.try_send((val, peer)).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We declare to the connected validators that we are a collator.
|
// We declare to the connected validators that we are a collator.
|
||||||
@@ -1531,15 +1503,8 @@ mod tests {
|
|||||||
|
|
||||||
assert!(overseer_recv_with_timeout(&mut virtual_overseer, TIMEOUT).await.is_none());
|
assert!(overseer_recv_with_timeout(&mut virtual_overseer, TIMEOUT).await.is_none());
|
||||||
|
|
||||||
let DistributeCollation { mut connected, .. } =
|
let DistributeCollation { connected: _connected, .. } =
|
||||||
distribute_collation(&mut virtual_overseer, &test_state).await;
|
distribute_collation(&mut virtual_overseer, &test_state, true).await;
|
||||||
|
|
||||||
for (val, peer) in test_state.current_group_validator_authority_ids()
|
|
||||||
.into_iter()
|
|
||||||
.zip(test_state.current_group_validator_peer_ids())
|
|
||||||
{
|
|
||||||
connected.try_send((val, peer)).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send info about peer's view.
|
// Send info about peer's view.
|
||||||
overseer_send(
|
overseer_send(
|
||||||
@@ -1567,11 +1532,12 @@ mod tests {
|
|||||||
let mut virtual_overseer = test_harness.virtual_overseer;
|
let mut virtual_overseer = test_harness.virtual_overseer;
|
||||||
|
|
||||||
let peer = test_state.validator_peer_id[0].clone();
|
let peer = test_state.validator_peer_id[0].clone();
|
||||||
|
let validator_id = test_state.current_group_validator_authority_ids()[0].clone();
|
||||||
|
|
||||||
setup_system(&mut virtual_overseer, &test_state).await;
|
setup_system(&mut virtual_overseer, &test_state).await;
|
||||||
|
|
||||||
// A validator connected to us
|
// A validator connected to us
|
||||||
connect_peer(&mut virtual_overseer, peer.clone()).await;
|
connect_peer(&mut virtual_overseer, peer.clone(), Some(validator_id)).await;
|
||||||
expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await;
|
expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await;
|
||||||
virtual_overseer
|
virtual_overseer
|
||||||
})
|
})
|
||||||
@@ -1595,10 +1561,10 @@ mod tests {
|
|||||||
setup_system(&mut virtual_overseer, &test_state).await;
|
setup_system(&mut virtual_overseer, &test_state).await;
|
||||||
|
|
||||||
// A validator connected to us
|
// A validator connected to us
|
||||||
connect_peer(&mut virtual_overseer, peer.clone()).await;
|
connect_peer(&mut virtual_overseer, peer.clone(), Some(validator_id)).await;
|
||||||
|
|
||||||
// Connect the second validator
|
// Connect the second validator
|
||||||
connect_peer(&mut virtual_overseer, peer2.clone()).await;
|
connect_peer(&mut virtual_overseer, peer2.clone(), Some(validator_id2)).await;
|
||||||
|
|
||||||
expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await;
|
expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await;
|
||||||
expect_declare_msg(&mut virtual_overseer, &test_state, &peer2).await;
|
expect_declare_msg(&mut virtual_overseer, &test_state, &peer2).await;
|
||||||
@@ -1606,9 +1572,7 @@ mod tests {
|
|||||||
// And let it tell us that it is has the same view.
|
// And let it tell us that it is has the same view.
|
||||||
send_peer_view_change(&mut virtual_overseer, &peer2, vec![test_state.relay_parent]).await;
|
send_peer_view_change(&mut virtual_overseer, &peer2, vec![test_state.relay_parent]).await;
|
||||||
|
|
||||||
let mut connected = distribute_collation(&mut virtual_overseer, &test_state).await.connected;
|
let _connected = distribute_collation(&mut virtual_overseer, &test_state, false).await.connected;
|
||||||
connected.try_send((validator_id, peer.clone())).unwrap();
|
|
||||||
connected.try_send((validator_id2, peer2.clone())).unwrap();
|
|
||||||
|
|
||||||
expect_advertise_collation_msg(&mut virtual_overseer, &peer2, test_state.relay_parent).await;
|
expect_advertise_collation_msg(&mut virtual_overseer, &peer2, test_state.relay_parent).await;
|
||||||
|
|
||||||
@@ -1639,26 +1603,22 @@ mod tests {
|
|||||||
setup_system(&mut virtual_overseer, &test_state).await;
|
setup_system(&mut virtual_overseer, &test_state).await;
|
||||||
|
|
||||||
// A validator connected to us
|
// A validator connected to us
|
||||||
connect_peer(&mut virtual_overseer, peer.clone()).await;
|
connect_peer(&mut virtual_overseer, peer.clone(), Some(validator_id)).await;
|
||||||
|
|
||||||
// Connect the second validator
|
// Connect the second validator
|
||||||
connect_peer(&mut virtual_overseer, peer2.clone()).await;
|
connect_peer(&mut virtual_overseer, peer2.clone(), Some(validator_id2)).await;
|
||||||
|
|
||||||
expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await;
|
expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await;
|
||||||
expect_declare_msg(&mut virtual_overseer, &test_state, &peer2).await;
|
expect_declare_msg(&mut virtual_overseer, &test_state, &peer2).await;
|
||||||
|
|
||||||
let mut connected = distribute_collation(&mut virtual_overseer, &test_state).await.connected;
|
let _connected = distribute_collation(&mut virtual_overseer, &test_state, false).await.connected;
|
||||||
connected.try_send((validator_id.clone(), peer.clone())).unwrap();
|
|
||||||
connected.try_send((validator_id2.clone(), peer2.clone())).unwrap();
|
|
||||||
|
|
||||||
let old_relay_parent = test_state.relay_parent;
|
let old_relay_parent = test_state.relay_parent;
|
||||||
|
|
||||||
// Advance to a new round, while informing the subsystem that the old and the new relay parent are active.
|
// Advance to a new round, while informing the subsystem that the old and the new relay parent are active.
|
||||||
test_state.advance_to_new_round(&mut virtual_overseer, true).await;
|
test_state.advance_to_new_round(&mut virtual_overseer, true).await;
|
||||||
|
|
||||||
let mut connected = distribute_collation(&mut virtual_overseer, &test_state).await.connected;
|
let _connected = distribute_collation(&mut virtual_overseer, &test_state, true).await.connected;
|
||||||
connected.try_send((validator_id, peer.clone())).unwrap();
|
|
||||||
connected.try_send((validator_id2, peer2.clone())).unwrap();
|
|
||||||
|
|
||||||
send_peer_view_change(&mut virtual_overseer, &peer, vec![old_relay_parent]).await;
|
send_peer_view_change(&mut virtual_overseer, &peer, vec![old_relay_parent]).await;
|
||||||
expect_advertise_collation_msg(&mut virtual_overseer, &peer, old_relay_parent).await;
|
expect_advertise_collation_msg(&mut virtual_overseer, &peer, old_relay_parent).await;
|
||||||
@@ -1685,18 +1645,17 @@ mod tests {
|
|||||||
setup_system(&mut virtual_overseer, &test_state).await;
|
setup_system(&mut virtual_overseer, &test_state).await;
|
||||||
|
|
||||||
// A validator connected to us
|
// A validator connected to us
|
||||||
connect_peer(&mut virtual_overseer, peer.clone()).await;
|
connect_peer(&mut virtual_overseer, peer.clone(), Some(validator_id.clone())).await;
|
||||||
expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await;
|
expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await;
|
||||||
|
|
||||||
let mut connected = distribute_collation(&mut virtual_overseer, &test_state).await.connected;
|
let _ = distribute_collation(&mut virtual_overseer, &test_state, false).await.connected;
|
||||||
connected.try_send((validator_id.clone(), peer.clone())).unwrap();
|
|
||||||
|
|
||||||
send_peer_view_change(&mut virtual_overseer, &peer, vec![test_state.relay_parent]).await;
|
send_peer_view_change(&mut virtual_overseer, &peer, vec![test_state.relay_parent]).await;
|
||||||
expect_advertise_collation_msg(&mut virtual_overseer, &peer, test_state.relay_parent).await;
|
expect_advertise_collation_msg(&mut virtual_overseer, &peer, test_state.relay_parent).await;
|
||||||
|
|
||||||
// Disconnect and reconnect directly
|
// Disconnect and reconnect directly
|
||||||
disconnect_peer(&mut virtual_overseer, peer.clone()).await;
|
disconnect_peer(&mut virtual_overseer, peer.clone()).await;
|
||||||
connect_peer(&mut virtual_overseer, peer.clone()).await;
|
connect_peer(&mut virtual_overseer, peer.clone(), Some(validator_id)).await;
|
||||||
expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await;
|
expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await;
|
||||||
|
|
||||||
send_peer_view_change(&mut virtual_overseer, &peer, vec![test_state.relay_parent]).await;
|
send_peer_view_change(&mut virtual_overseer, &peer, vec![test_state.relay_parent]).await;
|
||||||
@@ -1717,11 +1676,12 @@ mod tests {
|
|||||||
let mut virtual_overseer = test_harness.virtual_overseer;
|
let mut virtual_overseer = test_harness.virtual_overseer;
|
||||||
|
|
||||||
let peer = test_state.current_group_validator_peer_ids()[0].clone();
|
let peer = test_state.current_group_validator_peer_ids()[0].clone();
|
||||||
|
let validator_id = test_state.current_group_validator_authority_ids()[0].clone();
|
||||||
|
|
||||||
setup_system(&mut virtual_overseer, &test_state).await;
|
setup_system(&mut virtual_overseer, &test_state).await;
|
||||||
|
|
||||||
// A validator connected to us
|
// A validator connected to us
|
||||||
connect_peer(&mut virtual_overseer, peer.clone()).await;
|
connect_peer(&mut virtual_overseer, peer.clone(), Some(validator_id)).await;
|
||||||
expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await;
|
expect_declare_msg(&mut virtual_overseer, &test_state, &peer).await;
|
||||||
|
|
||||||
overseer_send(
|
overseer_send(
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
// Copyright 2021 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/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
//! Error handling related code and Error/Result definitions.
|
||||||
|
|
||||||
|
use polkadot_node_primitives::UncheckedSignedFullStatement;
|
||||||
|
use polkadot_subsystem::SubsystemError;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use polkadot_node_subsystem_util::{Fault, runtime, unwrap_non_fatal};
|
||||||
|
|
||||||
|
use crate::LOG_TARGET;
|
||||||
|
|
||||||
|
/// General result.
|
||||||
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
/// Result for fatal only failures.
|
||||||
|
pub type FatalResult<T> = std::result::Result<T, Fatal>;
|
||||||
|
|
||||||
|
/// Errors for statement distribution.
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
#[error(transparent)]
|
||||||
|
pub struct Error(pub Fault<NonFatal, Fatal>);
|
||||||
|
|
||||||
|
impl From<NonFatal> for Error {
|
||||||
|
fn from(e: NonFatal) -> Self {
|
||||||
|
Self(Fault::from_non_fatal(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Fatal> for Error {
|
||||||
|
fn from(f: Fatal) -> Self {
|
||||||
|
Self(Fault::from_fatal(f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<runtime::Error> for Error {
|
||||||
|
fn from(o: runtime::Error) -> Self {
|
||||||
|
Self(Fault::from_other(o))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fatal runtime errors.
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Fatal {
|
||||||
|
/// Receiving subsystem message from overseer failed.
|
||||||
|
#[error("Receiving message from overseer failed")]
|
||||||
|
SubsystemReceive(#[source] SubsystemError),
|
||||||
|
|
||||||
|
/// Errors coming from runtime::Runtime.
|
||||||
|
#[error("Error while accessing runtime information")]
|
||||||
|
Runtime(#[from] #[source] runtime::Fatal),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Errors for fetching of runtime information.
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum NonFatal {
|
||||||
|
/// Signature was invalid on received statement.
|
||||||
|
#[error("CollationSeconded contained statement with invalid signature.")]
|
||||||
|
InvalidStatementSignature(UncheckedSignedFullStatement),
|
||||||
|
|
||||||
|
/// Errors coming from runtime::Runtime.
|
||||||
|
#[error("Error while accessing runtime information")]
|
||||||
|
Runtime(#[from] #[source] runtime::NonFatal),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Utility for eating top level errors and log them.
|
||||||
|
///
|
||||||
|
/// We basically always want to try and continue on error. This utility function is meant to
|
||||||
|
/// consume top-level errors by simply logging them.
|
||||||
|
pub fn log_error(result: Result<()>, ctx: &'static str)
|
||||||
|
-> FatalResult<()>
|
||||||
|
{
|
||||||
|
if let Some(error) = unwrap_non_fatal(result.map_err(|e| e.0))? {
|
||||||
|
tracing::warn!(target: LOG_TARGET, error = ?error, ctx)
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -22,41 +22,25 @@
|
|||||||
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use futures::{channel::oneshot, FutureExt, TryFutureExt};
|
use futures::{FutureExt, TryFutureExt};
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use sp_keystore::SyncCryptoStorePtr;
|
use sp_keystore::SyncCryptoStorePtr;
|
||||||
|
|
||||||
use polkadot_node_network_protocol::{PeerId, UnifiedReputationChange as Rep};
|
use polkadot_node_network_protocol::{PeerId, UnifiedReputationChange as Rep};
|
||||||
use polkadot_node_subsystem_util::{self as util, metrics::prometheus};
|
|
||||||
use polkadot_primitives::v1::CollatorPair;
|
use polkadot_primitives::v1::CollatorPair;
|
||||||
use polkadot_subsystem::{
|
use polkadot_subsystem::{
|
||||||
errors::RuntimeApiError,
|
|
||||||
messages::{AllMessages, CollatorProtocolMessage, NetworkBridgeMessage},
|
messages::{AllMessages, CollatorProtocolMessage, NetworkBridgeMessage},
|
||||||
SpawnedSubsystem, Subsystem, SubsystemContext, SubsystemError,
|
SpawnedSubsystem, Subsystem, SubsystemContext, SubsystemError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod error;
|
||||||
|
use error::Result;
|
||||||
|
|
||||||
mod collator_side;
|
mod collator_side;
|
||||||
mod validator_side;
|
mod validator_side;
|
||||||
|
|
||||||
const LOG_TARGET: &'static str = "parachain::collator-protocol";
|
const LOG_TARGET: &'static str = "parachain::collator-protocol";
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
enum Error {
|
|
||||||
#[error(transparent)]
|
|
||||||
Subsystem(#[from] SubsystemError),
|
|
||||||
#[error(transparent)]
|
|
||||||
Oneshot(#[from] oneshot::Canceled),
|
|
||||||
#[error(transparent)]
|
|
||||||
RuntimeApi(#[from] RuntimeApiError),
|
|
||||||
#[error(transparent)]
|
|
||||||
UtilError(#[from] util::Error),
|
|
||||||
#[error(transparent)]
|
|
||||||
Prometheus(#[from] prometheus::PrometheusError),
|
|
||||||
}
|
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, Error>;
|
|
||||||
|
|
||||||
/// A collator eviction policy - how fast to evict collators which are inactive.
|
/// A collator eviction policy - how fast to evict collators which are inactive.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct CollatorEvictionPolicy {
|
pub struct CollatorEvictionPolicy {
|
||||||
@@ -124,9 +108,7 @@ impl CollatorProtocolSubsystem {
|
|||||||
collator_pair,
|
collator_pair,
|
||||||
metrics,
|
metrics,
|
||||||
).await,
|
).await,
|
||||||
}.map_err(|e| {
|
}
|
||||||
SubsystemError::with_origin("collator-protocol", e).into()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ use polkadot_subsystem::{
|
|||||||
FromOverseer, OverseerSignal, PerLeafSpan, SubsystemContext, SubsystemSender,
|
FromOverseer, OverseerSignal, PerLeafSpan, SubsystemContext, SubsystemSender,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::error::Fatal;
|
||||||
|
|
||||||
use super::{modify_reputation, Result, LOG_TARGET};
|
use super::{modify_reputation, Result, LOG_TARGET};
|
||||||
|
|
||||||
const COST_UNEXPECTED_MESSAGE: Rep = Rep::CostMinor("An unexpected message");
|
const COST_UNEXPECTED_MESSAGE: Rep = Rep::CostMinor("An unexpected message");
|
||||||
@@ -540,6 +542,7 @@ async fn notify_collation_seconded(
|
|||||||
ctx: &mut impl SubsystemContext<Message = CollatorProtocolMessage>,
|
ctx: &mut impl SubsystemContext<Message = CollatorProtocolMessage>,
|
||||||
peer_data: &HashMap<PeerId, PeerData>,
|
peer_data: &HashMap<PeerId, PeerData>,
|
||||||
id: CollatorId,
|
id: CollatorId,
|
||||||
|
relay_parent: Hash,
|
||||||
statement: SignedFullStatement,
|
statement: SignedFullStatement,
|
||||||
) {
|
) {
|
||||||
if !matches!(statement.payload(), Statement::Seconded(_)) {
|
if !matches!(statement.payload(), Statement::Seconded(_)) {
|
||||||
@@ -552,7 +555,7 @@ async fn notify_collation_seconded(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(peer_id) = collator_peer_id(peer_data, &id) {
|
if let Some(peer_id) = collator_peer_id(peer_data, &id) {
|
||||||
let wire_message = protocol_v1::CollatorProtocolMessage::CollationSeconded(statement);
|
let wire_message = protocol_v1::CollatorProtocolMessage::CollationSeconded(relay_parent, statement.into());
|
||||||
|
|
||||||
ctx.send_message(AllMessages::NetworkBridge(
|
ctx.send_message(AllMessages::NetworkBridge(
|
||||||
NetworkBridgeMessage::SendCollationMessage(
|
NetworkBridgeMessage::SendCollationMessage(
|
||||||
@@ -782,7 +785,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CollationSeconded(_) => {
|
CollationSeconded(_, _) => {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
peer_id = ?origin,
|
peer_id = ?origin,
|
||||||
@@ -934,8 +937,8 @@ where
|
|||||||
NoteGoodCollation(id) => {
|
NoteGoodCollation(id) => {
|
||||||
note_good_collation(ctx, &state.peer_data, id).await;
|
note_good_collation(ctx, &state.peer_data, id).await;
|
||||||
}
|
}
|
||||||
NotifyCollationSeconded(id, statement) => {
|
NotifyCollationSeconded(id, relay_parent, statement) => {
|
||||||
notify_collation_seconded(ctx, &state.peer_data, id, statement).await;
|
notify_collation_seconded(ctx, &state.peer_data, id, relay_parent, statement).await;
|
||||||
}
|
}
|
||||||
NetworkBridgeUpdateV1(event) => {
|
NetworkBridgeUpdateV1(event) => {
|
||||||
if let Err(e) = handle_network_msg(
|
if let Err(e) = handle_network_msg(
|
||||||
@@ -1003,7 +1006,7 @@ pub(crate) async fn run<Context>(
|
|||||||
|
|
||||||
if let Poll::Ready(res) = futures::poll!(s) {
|
if let Poll::Ready(res) = futures::poll!(s) {
|
||||||
Some(match res {
|
Some(match res {
|
||||||
Either::Left((msg, _)) => Either::Left(msg?),
|
Either::Left((msg, _)) => Either::Left(msg.map_err(Fatal::SubsystemReceive)?),
|
||||||
Either::Right((_, _)) => Either::Right(()),
|
Either::Right((_, _)) => Either::Right(()),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -291,18 +291,23 @@ pub mod v1 {
|
|||||||
use parity_scale_codec::{Encode, Decode};
|
use parity_scale_codec::{Encode, Decode};
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use polkadot_primitives::v1::{CandidateHash, CandidateIndex, CollatorId, CollatorSignature, CompactStatement, Hash, Id as ParaId, SignedAvailabilityBitfield, ValidatorIndex, ValidatorSignature};
|
use polkadot_primitives::v1::{
|
||||||
|
CandidateHash, CandidateIndex, CollatorId, CollatorSignature,
|
||||||
|
CompactStatement, Hash, Id as ParaId, UncheckedSignedAvailabilityBitfield,
|
||||||
|
ValidatorIndex, ValidatorSignature
|
||||||
|
};
|
||||||
use polkadot_node_primitives::{
|
use polkadot_node_primitives::{
|
||||||
approval::{IndirectAssignmentCert, IndirectSignedApprovalVote},
|
approval::{IndirectAssignmentCert, IndirectSignedApprovalVote},
|
||||||
SignedFullStatement,
|
UncheckedSignedFullStatement,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/// Network messages used by the bitfield distribution subsystem.
|
/// Network messages used by the bitfield distribution subsystem.
|
||||||
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
|
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
|
||||||
pub enum BitfieldDistributionMessage {
|
pub enum BitfieldDistributionMessage {
|
||||||
/// A signed availability bitfield for a given relay-parent hash.
|
/// A signed availability bitfield for a given relay-parent hash.
|
||||||
#[codec(index = 0)]
|
#[codec(index = 0)]
|
||||||
Bitfield(Hash, SignedAvailabilityBitfield),
|
Bitfield(Hash, UncheckedSignedAvailabilityBitfield),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Network messages used by the statement distribution subsystem.
|
/// Network messages used by the statement distribution subsystem.
|
||||||
@@ -310,7 +315,7 @@ pub mod v1 {
|
|||||||
pub enum StatementDistributionMessage {
|
pub enum StatementDistributionMessage {
|
||||||
/// A signed full statement under a given relay-parent.
|
/// A signed full statement under a given relay-parent.
|
||||||
#[codec(index = 0)]
|
#[codec(index = 0)]
|
||||||
Statement(Hash, SignedFullStatement),
|
Statement(Hash, UncheckedSignedFullStatement),
|
||||||
/// Seconded statement with large payload (e.g. containing a runtime upgrade).
|
/// Seconded statement with large payload (e.g. containing a runtime upgrade).
|
||||||
///
|
///
|
||||||
/// We only gossip the hash in that case, actual payloads can be fetched from sending node
|
/// We only gossip the hash in that case, actual payloads can be fetched from sending node
|
||||||
@@ -338,9 +343,9 @@ pub mod v1 {
|
|||||||
match self {
|
match self {
|
||||||
Self::Statement(relay_parent, statement) => StatementMetadata {
|
Self::Statement(relay_parent, statement) => StatementMetadata {
|
||||||
relay_parent: *relay_parent,
|
relay_parent: *relay_parent,
|
||||||
candidate_hash: statement.payload().candidate_hash(),
|
candidate_hash: statement.unchecked_payload().candidate_hash(),
|
||||||
signed_by: statement.validator_index(),
|
signed_by: statement.unchecked_validator_index(),
|
||||||
signature: statement.signature().clone(),
|
signature: statement.unchecked_signature().clone(),
|
||||||
},
|
},
|
||||||
Self::LargeStatement(metadata) => metadata.clone(),
|
Self::LargeStatement(metadata) => metadata.clone(),
|
||||||
}
|
}
|
||||||
@@ -350,7 +355,7 @@ pub mod v1 {
|
|||||||
pub fn get_fingerprint(&self) -> (CompactStatement, ValidatorIndex) {
|
pub fn get_fingerprint(&self) -> (CompactStatement, ValidatorIndex) {
|
||||||
match self {
|
match self {
|
||||||
Self::Statement(_, statement) =>
|
Self::Statement(_, statement) =>
|
||||||
(statement.payload().to_compact(), statement.validator_index()),
|
(statement.unchecked_payload().to_compact(), statement.unchecked_validator_index()),
|
||||||
Self::LargeStatement(meta) =>
|
Self::LargeStatement(meta) =>
|
||||||
(CompactStatement::Seconded(meta.candidate_hash), meta.signed_by),
|
(CompactStatement::Seconded(meta.candidate_hash), meta.signed_by),
|
||||||
}
|
}
|
||||||
@@ -400,7 +405,7 @@ pub mod v1 {
|
|||||||
AdvertiseCollation(Hash),
|
AdvertiseCollation(Hash),
|
||||||
/// A collation sent to a validator was seconded.
|
/// A collation sent to a validator was seconded.
|
||||||
#[codec(index = 4)]
|
#[codec(index = 4)]
|
||||||
CollationSeconded(SignedFullStatement),
|
CollationSeconded(Hash, UncheckedSignedFullStatement),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// All network messages on the validation peer-set.
|
/// All network messages on the validation peer-set.
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ use polkadot_node_subsystem_util::{
|
|||||||
metrics::{self, prometheus},
|
metrics::{self, prometheus},
|
||||||
self as util, MIN_GOSSIP_PEERS,
|
self as util, MIN_GOSSIP_PEERS,
|
||||||
};
|
};
|
||||||
use polkadot_node_primitives::{SignedFullStatement, Statement};
|
use polkadot_node_primitives::{SignedFullStatement, UncheckedSignedFullStatement, Statement};
|
||||||
use polkadot_primitives::v1::{
|
use polkadot_primitives::v1::{
|
||||||
CandidateHash, CommittedCandidateReceipt, CompactStatement, Hash,
|
CandidateHash, CommittedCandidateReceipt, CompactStatement, Hash,
|
||||||
SigningContext, ValidatorId, ValidatorIndex, ValidatorSignature, AuthorityDiscoveryId,
|
SigningContext, ValidatorId, ValidatorIndex, ValidatorSignature, AuthorityDiscoveryId,
|
||||||
@@ -54,7 +54,7 @@ use polkadot_node_network_protocol::{
|
|||||||
|
|
||||||
use futures::{channel::mpsc, future::RemoteHandle, prelude::*};
|
use futures::{channel::mpsc, future::RemoteHandle, prelude::*};
|
||||||
use futures::channel::oneshot;
|
use futures::channel::oneshot;
|
||||||
use indexmap::{IndexSet, IndexMap, map::Entry as IEntry};
|
use indexmap::{IndexMap, map::Entry as IEntry};
|
||||||
use sp_keystore::SyncCryptoStorePtr;
|
use sp_keystore::SyncCryptoStorePtr;
|
||||||
use util::{Fault, runtime::RuntimeInfo};
|
use util::{Fault, runtime::RuntimeInfo};
|
||||||
|
|
||||||
@@ -461,10 +461,10 @@ impl PeerData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// A statement stored while a relay chain head is active.
|
// A statement stored while a relay chain head is active.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
struct StoredStatement {
|
struct StoredStatement<'a> {
|
||||||
comparator: StoredStatementComparator,
|
comparator: &'a StoredStatementComparator,
|
||||||
statement: SignedFullStatement,
|
statement: &'a SignedFullStatement,
|
||||||
}
|
}
|
||||||
|
|
||||||
// A value used for comparison of stored statements to each other.
|
// A value used for comparison of stored statements to each other.
|
||||||
@@ -480,8 +480,14 @@ struct StoredStatementComparator {
|
|||||||
signature: ValidatorSignature,
|
signature: ValidatorSignature,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StoredStatement {
|
impl<'a> From<(&'a StoredStatementComparator, &'a SignedFullStatement)> for StoredStatement<'a> {
|
||||||
fn compact(&self) -> &CompactStatement {
|
fn from((comparator, statement): (&'a StoredStatementComparator, &'a SignedFullStatement)) -> Self {
|
||||||
|
Self { comparator, statement }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> StoredStatement<'a> {
|
||||||
|
fn compact(&self) -> &'a CompactStatement {
|
||||||
&self.comparator.compact
|
&self.comparator.compact
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -490,30 +496,10 @@ impl StoredStatement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::borrow::Borrow<StoredStatementComparator> for StoredStatement {
|
|
||||||
fn borrow(&self) -> &StoredStatementComparator {
|
|
||||||
&self.comparator
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::hash::Hash for StoredStatement {
|
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
self.comparator.hash(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::cmp::PartialEq for StoredStatement {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
&self.comparator == &other.comparator
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::cmp::Eq for StoredStatement {}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum NotedStatement<'a> {
|
enum NotedStatement<'a> {
|
||||||
NotUseful,
|
NotUseful,
|
||||||
Fresh(&'a StoredStatement),
|
Fresh(StoredStatement<'a>),
|
||||||
UsefulButKnown
|
UsefulButKnown
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -588,7 +574,7 @@ struct ActiveHeadData {
|
|||||||
///
|
///
|
||||||
/// These are iterable in insertion order, and `Seconded` statements are always
|
/// These are iterable in insertion order, and `Seconded` statements are always
|
||||||
/// accepted before dependent statements.
|
/// accepted before dependent statements.
|
||||||
statements: IndexSet<StoredStatement>,
|
statements: IndexMap<StoredStatementComparator, SignedFullStatement>,
|
||||||
/// Large statements we are waiting for with associated meta data.
|
/// Large statements we are waiting for with associated meta data.
|
||||||
waiting_large_statements: HashMap<CandidateHash, LargeStatementStatus>,
|
waiting_large_statements: HashMap<CandidateHash, LargeStatementStatus>,
|
||||||
/// The validators at this head.
|
/// The validators at this head.
|
||||||
@@ -641,11 +627,6 @@ impl ActiveHeadData {
|
|||||||
signature: statement.signature().clone(),
|
signature: statement.signature().clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let stored = StoredStatement {
|
|
||||||
comparator: comparator.clone(),
|
|
||||||
statement,
|
|
||||||
};
|
|
||||||
|
|
||||||
match comparator.compact {
|
match comparator.compact {
|
||||||
CompactStatement::Seconded(h) => {
|
CompactStatement::Seconded(h) => {
|
||||||
let seconded_so_far = self.seconded_counts.entry(validator_index).or_insert(0);
|
let seconded_so_far = self.seconded_counts.entry(validator_index).or_insert(0);
|
||||||
@@ -653,34 +634,36 @@ impl ActiveHeadData {
|
|||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
?validator_index,
|
?validator_index,
|
||||||
statement = ?stored.statement,
|
?statement,
|
||||||
"Extra statement is ignored"
|
"Extra statement is ignored"
|
||||||
);
|
);
|
||||||
return NotedStatement::NotUseful;
|
return NotedStatement::NotUseful;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.candidates.insert(h);
|
self.candidates.insert(h);
|
||||||
if self.statements.insert(stored) {
|
if let Some(old) = self.statements.insert(comparator.clone(), statement) {
|
||||||
|
tracing::trace!(
|
||||||
|
target: LOG_TARGET,
|
||||||
|
?validator_index,
|
||||||
|
statement = ?old,
|
||||||
|
"Known statement"
|
||||||
|
);
|
||||||
|
NotedStatement::UsefulButKnown
|
||||||
|
} else {
|
||||||
*seconded_so_far += 1;
|
*seconded_so_far += 1;
|
||||||
|
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
?validator_index,
|
?validator_index,
|
||||||
statement = ?self.statements.last().expect("Just inserted").statement,
|
statement = ?self.statements.last().expect("Just inserted").1,
|
||||||
"Noted new statement"
|
"Noted new statement"
|
||||||
);
|
);
|
||||||
// This will always return `Some` because it was just inserted.
|
// This will always return `Some` because it was just inserted.
|
||||||
NotedStatement::Fresh(self.statements.get(&comparator)
|
let key_value = self.statements
|
||||||
.expect("Statement was just inserted; qed"))
|
.get_key_value(&comparator)
|
||||||
} else {
|
.expect("Statement was just inserted; qed");
|
||||||
tracing::trace!(
|
|
||||||
target: LOG_TARGET,
|
NotedStatement::Fresh(key_value.into())
|
||||||
?validator_index,
|
|
||||||
statement = ?self.statements.get(&comparator)
|
|
||||||
.expect("Existence was just checked; qed").statement,
|
|
||||||
"Known statement"
|
|
||||||
);
|
|
||||||
NotedStatement::UsefulButKnown
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CompactStatement::Valid(h) => {
|
CompactStatement::Valid(h) => {
|
||||||
@@ -688,31 +671,34 @@ impl ActiveHeadData {
|
|||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
?validator_index,
|
?validator_index,
|
||||||
statement = ?stored.statement,
|
?statement,
|
||||||
"Statement for unknown candidate"
|
"Statement for unknown candidate"
|
||||||
);
|
);
|
||||||
return NotedStatement::NotUseful;
|
return NotedStatement::NotUseful;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.statements.insert(stored) {
|
if let Some(old) = self.statements.insert(comparator.clone(), statement) {
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
?validator_index,
|
?validator_index,
|
||||||
statement = ?self.statements.last().expect("Just inserted").statement,
|
statement = ?old,
|
||||||
"Noted new statement"
|
"Known statement"
|
||||||
);
|
);
|
||||||
// This will always return `Some` because it was just inserted.
|
NotedStatement::UsefulButKnown
|
||||||
NotedStatement::Fresh(self.statements.get(&comparator)
|
|
||||||
.expect("Statement was just inserted; qed"))
|
|
||||||
} else {
|
} else {
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
?validator_index,
|
?validator_index,
|
||||||
statement = ?self.statements.get(&comparator)
|
statement = ?self.statements.last().expect("Just inserted").1,
|
||||||
.expect("Existence was just checked; qed").statement,
|
"Noted new statement"
|
||||||
"Known statement"
|
|
||||||
);
|
);
|
||||||
NotedStatement::UsefulButKnown
|
// This will always return `Some` because it was just inserted.
|
||||||
|
NotedStatement::Fresh(
|
||||||
|
self.statements
|
||||||
|
.get_key_value(&comparator)
|
||||||
|
.expect("Statement was just inserted; qed")
|
||||||
|
.into()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -720,20 +706,15 @@ impl ActiveHeadData {
|
|||||||
|
|
||||||
/// Returns an error if the statement is already known or not useful
|
/// Returns an error if the statement is already known or not useful
|
||||||
/// without modifying the internal state.
|
/// without modifying the internal state.
|
||||||
fn check_useful_or_unknown(&self, statement: SignedFullStatement)
|
fn check_useful_or_unknown(&self, statement: &UncheckedSignedFullStatement)
|
||||||
-> std::result::Result<(), DeniedStatement>
|
-> std::result::Result<(), DeniedStatement>
|
||||||
{
|
{
|
||||||
let validator_index = statement.validator_index();
|
let validator_index = statement.unchecked_validator_index();
|
||||||
let compact = statement.payload().to_compact();
|
let compact = statement.unchecked_payload().to_compact();
|
||||||
let comparator = StoredStatementComparator {
|
let comparator = StoredStatementComparator {
|
||||||
compact: compact.clone(),
|
compact: compact.clone(),
|
||||||
validator_index,
|
validator_index,
|
||||||
signature: statement.signature().clone(),
|
signature: statement.unchecked_signature().clone(),
|
||||||
};
|
|
||||||
|
|
||||||
let stored = StoredStatement {
|
|
||||||
comparator,
|
|
||||||
statement,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
match compact {
|
match compact {
|
||||||
@@ -743,17 +724,17 @@ impl ActiveHeadData {
|
|||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
?validator_index,
|
?validator_index,
|
||||||
statement = ?stored.statement,
|
?statement,
|
||||||
"Extra statement is ignored",
|
"Extra statement is ignored",
|
||||||
);
|
);
|
||||||
return Err(DeniedStatement::NotUseful);
|
return Err(DeniedStatement::NotUseful);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.statements.contains(&stored) {
|
if self.statements.contains_key(&comparator) {
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
?validator_index,
|
?validator_index,
|
||||||
statement = ?stored.statement,
|
?statement,
|
||||||
"Known statement",
|
"Known statement",
|
||||||
);
|
);
|
||||||
return Err(DeniedStatement::UsefulButKnown);
|
return Err(DeniedStatement::UsefulButKnown);
|
||||||
@@ -764,17 +745,17 @@ impl ActiveHeadData {
|
|||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
?validator_index,
|
?validator_index,
|
||||||
statement = ?stored.statement,
|
?statement,
|
||||||
"Statement for unknown candidate",
|
"Statement for unknown candidate",
|
||||||
);
|
);
|
||||||
return Err(DeniedStatement::NotUseful);
|
return Err(DeniedStatement::NotUseful);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.statements.contains(&stored) {
|
if self.statements.contains_key(&comparator) {
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
?validator_index,
|
?validator_index,
|
||||||
statement = ?stored.statement,
|
?statement,
|
||||||
"Known statement",
|
"Known statement",
|
||||||
);
|
);
|
||||||
return Err(DeniedStatement::UsefulButKnown);
|
return Err(DeniedStatement::UsefulButKnown);
|
||||||
@@ -785,14 +766,13 @@ impl ActiveHeadData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get an iterator over all statements for the active head. Seconded statements come first.
|
/// Get an iterator over all statements for the active head. Seconded statements come first.
|
||||||
fn statements(&self) -> impl Iterator<Item = &'_ StoredStatement> + '_ {
|
fn statements(&self) -> impl Iterator<Item = StoredStatement<'_>> + '_ {
|
||||||
self.statements.iter()
|
self.statements.iter().map(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an iterator over all statements for the active head that are for a particular candidate.
|
/// Get an iterator over all statements for the active head that are for a particular candidate.
|
||||||
fn statements_about(&self, candidate_hash: CandidateHash)
|
fn statements_about(&self, candidate_hash: CandidateHash)
|
||||||
-> impl Iterator<Item = &'_ StoredStatement> + '_
|
-> impl Iterator<Item = StoredStatement<'_>> + '_ {
|
||||||
{
|
|
||||||
self.statements().filter(move |s| s.compact().candidate_hash() == &candidate_hash)
|
self.statements().filter(move |s| s.compact().candidate_hash() == &candidate_hash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -801,16 +781,17 @@ impl ActiveHeadData {
|
|||||||
fn check_statement_signature(
|
fn check_statement_signature(
|
||||||
head: &ActiveHeadData,
|
head: &ActiveHeadData,
|
||||||
relay_parent: Hash,
|
relay_parent: Hash,
|
||||||
statement: &SignedFullStatement,
|
statement: UncheckedSignedFullStatement,
|
||||||
) -> std::result::Result<(), ()> {
|
) -> std::result::Result<SignedFullStatement, UncheckedSignedFullStatement> {
|
||||||
let signing_context = SigningContext {
|
let signing_context = SigningContext {
|
||||||
session_index: head.session_index,
|
session_index: head.session_index,
|
||||||
parent_hash: relay_parent,
|
parent_hash: relay_parent,
|
||||||
};
|
};
|
||||||
|
|
||||||
head.validators.get(statement.validator_index().0 as usize)
|
head.validators
|
||||||
.ok_or(())
|
.get(statement.unchecked_validator_index().0 as usize)
|
||||||
.and_then(|v| statement.check_signature(&signing_context, v))
|
.ok_or_else(|| statement.clone())
|
||||||
|
.and_then(|v| statement.try_into_checked(&signing_context, v))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Places the statement in storage if it is new, and then
|
/// Places the statement in storage if it is new, and then
|
||||||
@@ -887,7 +868,7 @@ fn statement_message(relay_parent: Hash, statement: SignedFullStatement)
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
protocol_v1::StatementDistributionMessage::Statement(relay_parent, statement)
|
protocol_v1::StatementDistributionMessage::Statement(relay_parent, statement.into())
|
||||||
};
|
};
|
||||||
|
|
||||||
protocol_v1::ValidationProtocol::StatementDistribution(msg)
|
protocol_v1::ValidationProtocol::StatementDistribution(msg)
|
||||||
@@ -902,7 +883,7 @@ fn is_statement_large(statement: &SignedFullStatement) -> bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// No runtime upgrade, now we need to be more nuanced:
|
// No runtime upgrade, now we need to be more nuanced:
|
||||||
let size = statement.encoded_size();
|
let size = statement.as_unchecked().encoded_size();
|
||||||
|
|
||||||
// Half max size seems to be a good threshold to start not using notifications:
|
// Half max size seems to be a good threshold to start not using notifications:
|
||||||
let threshold =
|
let threshold =
|
||||||
@@ -919,11 +900,11 @@ fn is_statement_large(statement: &SignedFullStatement) -> bool {
|
|||||||
/// Circulates a statement to all peers who have not seen it yet, and returns
|
/// Circulates a statement to all peers who have not seen it yet, and returns
|
||||||
/// an iterator over peers who need to have dependent statements sent.
|
/// an iterator over peers who need to have dependent statements sent.
|
||||||
#[tracing::instrument(level = "trace", skip(peers, ctx), fields(subsystem = LOG_TARGET))]
|
#[tracing::instrument(level = "trace", skip(peers, ctx), fields(subsystem = LOG_TARGET))]
|
||||||
async fn circulate_statement(
|
async fn circulate_statement<'a>(
|
||||||
peers: &mut HashMap<PeerId, PeerData>,
|
peers: &mut HashMap<PeerId, PeerData>,
|
||||||
ctx: &mut impl SubsystemContext,
|
ctx: &mut impl SubsystemContext,
|
||||||
relay_parent: Hash,
|
relay_parent: Hash,
|
||||||
stored: &StoredStatement,
|
stored: StoredStatement<'a>,
|
||||||
mut priority_peers: Vec<PeerId>,
|
mut priority_peers: Vec<PeerId>,
|
||||||
) -> Vec<PeerId> {
|
) -> Vec<PeerId> {
|
||||||
let fingerprint = stored.fingerprint();
|
let fingerprint = stored.fingerprint();
|
||||||
@@ -1092,7 +1073,7 @@ async fn retrieve_statement_from_message<'a>(
|
|||||||
ctx: &mut impl SubsystemContext,
|
ctx: &mut impl SubsystemContext,
|
||||||
req_sender: &mpsc::Sender<RequesterMessage>,
|
req_sender: &mpsc::Sender<RequesterMessage>,
|
||||||
metrics: &Metrics,
|
metrics: &Metrics,
|
||||||
) -> Option<SignedFullStatement> {
|
) -> Option<UncheckedSignedFullStatement> {
|
||||||
|
|
||||||
let fingerprint = message.get_fingerprint();
|
let fingerprint = message.get_fingerprint();
|
||||||
let candidate_hash = *fingerprint.0.candidate_hash();
|
let candidate_hash = *fingerprint.0.candidate_hash();
|
||||||
@@ -1100,7 +1081,7 @@ async fn retrieve_statement_from_message<'a>(
|
|||||||
// Immediately return any Seconded statement:
|
// Immediately return any Seconded statement:
|
||||||
let message =
|
let message =
|
||||||
if let protocol_v1::StatementDistributionMessage::Statement(h, s) = message {
|
if let protocol_v1::StatementDistributionMessage::Statement(h, s) = message {
|
||||||
if let Statement::Seconded(_) = s.payload() {
|
if let Statement::Seconded(_) = s.unchecked_payload() {
|
||||||
return Some(s)
|
return Some(s)
|
||||||
}
|
}
|
||||||
protocol_v1::StatementDistributionMessage::Statement(h, s)
|
protocol_v1::StatementDistributionMessage::Statement(h, s)
|
||||||
@@ -1148,40 +1129,12 @@ async fn retrieve_statement_from_message<'a>(
|
|||||||
return Some(s)
|
return Some(s)
|
||||||
}
|
}
|
||||||
protocol_v1::StatementDistributionMessage::LargeStatement(metadata) => {
|
protocol_v1::StatementDistributionMessage::LargeStatement(metadata) => {
|
||||||
|
return Some(UncheckedSignedFullStatement::new(
|
||||||
let validator_id = active_head.validators.get(metadata.signed_by.0 as usize);
|
Statement::Seconded(
|
||||||
|
committed.clone()),
|
||||||
if let Some(validator_id) = validator_id {
|
|
||||||
let signing_context = SigningContext {
|
|
||||||
session_index: active_head.session_index,
|
|
||||||
parent_hash: metadata.relay_parent,
|
|
||||||
};
|
|
||||||
|
|
||||||
let statement = SignedFullStatement::new(
|
|
||||||
Statement::Seconded(committed.clone()),
|
|
||||||
metadata.signed_by,
|
metadata.signed_by,
|
||||||
metadata.signature.clone(),
|
metadata.signature.clone(),
|
||||||
&signing_context,
|
))
|
||||||
validator_id,
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(statement) = statement {
|
|
||||||
return Some(statement)
|
|
||||||
} else {
|
|
||||||
tracing::debug!(
|
|
||||||
target: LOG_TARGET,
|
|
||||||
validator_index = ?metadata.signed_by,
|
|
||||||
"Building statement failed - invalid signature!"
|
|
||||||
);
|
|
||||||
report_peer(ctx, peer, COST_INVALID_SIGNATURE).await;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tracing::debug!(
|
|
||||||
target: LOG_TARGET,
|
|
||||||
validator_index = ?metadata.signed_by,
|
|
||||||
"Error loading statement, could not find key for validator."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1309,7 +1262,7 @@ async fn handle_incoming_message<'a>(
|
|||||||
message: protocol_v1::StatementDistributionMessage,
|
message: protocol_v1::StatementDistributionMessage,
|
||||||
req_sender: &mpsc::Sender<RequesterMessage>,
|
req_sender: &mpsc::Sender<RequesterMessage>,
|
||||||
metrics: &Metrics,
|
metrics: &Metrics,
|
||||||
) -> Option<(Hash, &'a StoredStatement)> {
|
) -> Option<(Hash, StoredStatement<'a>)> {
|
||||||
let relay_parent = message.get_relay_parent();
|
let relay_parent = message.get_relay_parent();
|
||||||
|
|
||||||
let active_head = match active_heads.get_mut(&relay_parent) {
|
let active_head = match active_heads.get_mut(&relay_parent) {
|
||||||
@@ -1356,7 +1309,7 @@ async fn handle_incoming_message<'a>(
|
|||||||
metrics,
|
metrics,
|
||||||
).await?;
|
).await?;
|
||||||
|
|
||||||
match active_head.check_useful_or_unknown(statement.clone()) {
|
match active_head.check_useful_or_unknown(&statement) {
|
||||||
Ok(()) => {},
|
Ok(()) => {},
|
||||||
Err(DeniedStatement::NotUseful) => {
|
Err(DeniedStatement::NotUseful) => {
|
||||||
return None;
|
return None;
|
||||||
@@ -1368,7 +1321,8 @@ async fn handle_incoming_message<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check the signature on the statement.
|
// check the signature on the statement.
|
||||||
if let Err(()) = check_statement_signature(&active_head, relay_parent, &statement) {
|
let statement = match check_statement_signature(&active_head, relay_parent, statement) {
|
||||||
|
Err(statement) => {
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
?peer,
|
?peer,
|
||||||
@@ -1376,8 +1330,10 @@ async fn handle_incoming_message<'a>(
|
|||||||
"Invalid statement signature"
|
"Invalid statement signature"
|
||||||
);
|
);
|
||||||
report_peer(ctx, peer, COST_INVALID_SIGNATURE).await;
|
report_peer(ctx, peer, COST_INVALID_SIGNATURE).await;
|
||||||
return None;
|
return None
|
||||||
}
|
}
|
||||||
|
Ok(statement) => statement,
|
||||||
|
};
|
||||||
|
|
||||||
// Ensure the statement is stored in the peer data.
|
// Ensure the statement is stored in the peer data.
|
||||||
//
|
//
|
||||||
@@ -1553,7 +1509,7 @@ impl StatementDistribution {
|
|||||||
let mut authorities: HashMap<AuthorityDiscoveryId, PeerId> = HashMap::new();
|
let mut authorities: HashMap<AuthorityDiscoveryId, PeerId> = HashMap::new();
|
||||||
let mut active_heads: HashMap<Hash, ActiveHeadData> = HashMap::new();
|
let mut active_heads: HashMap<Hash, ActiveHeadData> = HashMap::new();
|
||||||
|
|
||||||
let mut runtime = RuntimeInfo::new(self.keystore.clone());
|
let mut runtime = RuntimeInfo::new(Some(self.keystore.clone()));
|
||||||
|
|
||||||
// Sender/Receiver for getting news from our statement fetching tasks.
|
// Sender/Receiver for getting news from our statement fetching tasks.
|
||||||
let (req_sender, mut req_receiver) = mpsc::channel(1);
|
let (req_sender, mut req_receiver) = mpsc::channel(1);
|
||||||
@@ -2116,14 +2072,14 @@ mod tests {
|
|||||||
ValidatorIndex(0),
|
ValidatorIndex(0),
|
||||||
&alice_public.into(),
|
&alice_public.into(),
|
||||||
)).ok().flatten().expect("should be signed");
|
)).ok().flatten().expect("should be signed");
|
||||||
assert!(head_data.check_useful_or_unknown(a_seconded_val_0.clone()).is_ok());
|
assert!(head_data.check_useful_or_unknown(&a_seconded_val_0.clone().into()).is_ok());
|
||||||
let noted = head_data.note_statement(a_seconded_val_0.clone());
|
let noted = head_data.note_statement(a_seconded_val_0.clone());
|
||||||
|
|
||||||
assert_matches!(noted, NotedStatement::Fresh(_));
|
assert_matches!(noted, NotedStatement::Fresh(_));
|
||||||
|
|
||||||
// note A (duplicate)
|
// note A (duplicate)
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
head_data.check_useful_or_unknown(a_seconded_val_0.clone()),
|
head_data.check_useful_or_unknown(&a_seconded_val_0.clone().into()),
|
||||||
Err(DeniedStatement::UsefulButKnown),
|
Err(DeniedStatement::UsefulButKnown),
|
||||||
);
|
);
|
||||||
let noted = head_data.note_statement(a_seconded_val_0);
|
let noted = head_data.note_statement(a_seconded_val_0);
|
||||||
@@ -2138,7 +2094,7 @@ mod tests {
|
|||||||
ValidatorIndex(0),
|
ValidatorIndex(0),
|
||||||
&alice_public.into(),
|
&alice_public.into(),
|
||||||
)).ok().flatten().expect("should be signed");
|
)).ok().flatten().expect("should be signed");
|
||||||
assert!(head_data.check_useful_or_unknown(statement.clone()).is_ok());
|
assert!(head_data.check_useful_or_unknown(&statement.clone().into()).is_ok());
|
||||||
let noted = head_data.note_statement(statement);
|
let noted = head_data.note_statement(statement);
|
||||||
assert_matches!(noted, NotedStatement::Fresh(_));
|
assert_matches!(noted, NotedStatement::Fresh(_));
|
||||||
|
|
||||||
@@ -2151,7 +2107,7 @@ mod tests {
|
|||||||
&alice_public.into(),
|
&alice_public.into(),
|
||||||
)).ok().flatten().expect("should be signed");
|
)).ok().flatten().expect("should be signed");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
head_data.check_useful_or_unknown(statement.clone()),
|
head_data.check_useful_or_unknown(&statement.clone().into()),
|
||||||
Err(DeniedStatement::NotUseful),
|
Err(DeniedStatement::NotUseful),
|
||||||
);
|
);
|
||||||
let noted = head_data.note_statement(statement);
|
let noted = head_data.note_statement(statement);
|
||||||
@@ -2165,7 +2121,7 @@ mod tests {
|
|||||||
ValidatorIndex(1),
|
ValidatorIndex(1),
|
||||||
&bob_public.into(),
|
&bob_public.into(),
|
||||||
)).ok().flatten().expect("should be signed");
|
)).ok().flatten().expect("should be signed");
|
||||||
assert!(head_data.check_useful_or_unknown(statement.clone()).is_ok());
|
assert!(head_data.check_useful_or_unknown(&statement.clone().into()).is_ok());
|
||||||
let noted = head_data.note_statement(statement);
|
let noted = head_data.note_statement(statement);
|
||||||
assert_matches!(noted, NotedStatement::Fresh(_));
|
assert_matches!(noted, NotedStatement::Fresh(_));
|
||||||
|
|
||||||
@@ -2177,7 +2133,7 @@ mod tests {
|
|||||||
ValidatorIndex(1),
|
ValidatorIndex(1),
|
||||||
&bob_public.into(),
|
&bob_public.into(),
|
||||||
)).ok().flatten().expect("should be signed");
|
)).ok().flatten().expect("should be signed");
|
||||||
assert!(head_data.check_useful_or_unknown(statement.clone()).is_ok());
|
assert!(head_data.check_useful_or_unknown(&statement.clone().into()).is_ok());
|
||||||
let noted = head_data.note_statement(statement);
|
let noted = head_data.note_statement(statement);
|
||||||
assert_matches!(noted, NotedStatement::Fresh(_));
|
assert_matches!(noted, NotedStatement::Fresh(_));
|
||||||
}
|
}
|
||||||
@@ -2406,7 +2362,7 @@ mod tests {
|
|||||||
ValidatorIndex(0),
|
ValidatorIndex(0),
|
||||||
&alice_public.into(),
|
&alice_public.into(),
|
||||||
)).ok().flatten().expect("should be signed");
|
)).ok().flatten().expect("should be signed");
|
||||||
assert!(data.check_useful_or_unknown(statement.clone()).is_ok());
|
assert!(data.check_useful_or_unknown(&statement.clone().into()).is_ok());
|
||||||
let noted = data.note_statement(statement);
|
let noted = data.note_statement(statement);
|
||||||
|
|
||||||
assert_matches!(noted, NotedStatement::Fresh(_));
|
assert_matches!(noted, NotedStatement::Fresh(_));
|
||||||
@@ -2418,7 +2374,7 @@ mod tests {
|
|||||||
ValidatorIndex(1),
|
ValidatorIndex(1),
|
||||||
&bob_public.into(),
|
&bob_public.into(),
|
||||||
)).ok().flatten().expect("should be signed");
|
)).ok().flatten().expect("should be signed");
|
||||||
assert!(data.check_useful_or_unknown(statement.clone()).is_ok());
|
assert!(data.check_useful_or_unknown(&statement.clone().into()).is_ok());
|
||||||
let noted = data.note_statement(statement);
|
let noted = data.note_statement(statement);
|
||||||
|
|
||||||
assert_matches!(noted, NotedStatement::Fresh(_));
|
assert_matches!(noted, NotedStatement::Fresh(_));
|
||||||
@@ -2430,7 +2386,7 @@ mod tests {
|
|||||||
ValidatorIndex(2),
|
ValidatorIndex(2),
|
||||||
&charlie_public.into(),
|
&charlie_public.into(),
|
||||||
)).ok().flatten().expect("should be signed");
|
)).ok().flatten().expect("should be signed");
|
||||||
assert!(data.check_useful_or_unknown(statement.clone()).is_ok());
|
assert!(data.check_useful_or_unknown(&statement.clone().into()).is_ok());
|
||||||
let noted = data.note_statement(statement);
|
let noted = data.note_statement(statement);
|
||||||
assert_matches!(noted, NotedStatement::Fresh(_));
|
assert_matches!(noted, NotedStatement::Fresh(_));
|
||||||
|
|
||||||
@@ -2553,7 +2509,6 @@ mod tests {
|
|||||||
::<StatementDistributionMessage,_>(pool);
|
::<StatementDistributionMessage,_>(pool);
|
||||||
|
|
||||||
executor::block_on(async move {
|
executor::block_on(async move {
|
||||||
let statement = {
|
|
||||||
let signing_context = SigningContext {
|
let signing_context = SigningContext {
|
||||||
parent_hash: hash_b,
|
parent_hash: hash_b,
|
||||||
session_index,
|
session_index,
|
||||||
@@ -2572,21 +2527,21 @@ mod tests {
|
|||||||
&alice_public.into(),
|
&alice_public.into(),
|
||||||
).await.ok().flatten().expect("should be signed");
|
).await.ok().flatten().expect("should be signed");
|
||||||
|
|
||||||
StoredStatement {
|
let comparator = StoredStatementComparator {
|
||||||
comparator: StoredStatementComparator {
|
|
||||||
compact: statement.payload().to_compact(),
|
compact: statement.payload().to_compact(),
|
||||||
validator_index: ValidatorIndex(0),
|
validator_index: ValidatorIndex(0),
|
||||||
signature: statement.signature().clone()
|
signature: statement.signature().clone()
|
||||||
},
|
};
|
||||||
statement,
|
let statement = StoredStatement {
|
||||||
}
|
comparator: &comparator,
|
||||||
|
statement: &statement,
|
||||||
};
|
};
|
||||||
|
|
||||||
let needs_dependents = circulate_statement(
|
let needs_dependents = circulate_statement(
|
||||||
&mut peer_data,
|
&mut peer_data,
|
||||||
&mut ctx,
|
&mut ctx,
|
||||||
hash_b,
|
hash_b,
|
||||||
&statement,
|
statement,
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
).await;
|
).await;
|
||||||
|
|
||||||
@@ -2746,7 +2701,7 @@ mod tests {
|
|||||||
msg: StatementDistributionMessage::NetworkBridgeUpdateV1(
|
msg: StatementDistributionMessage::NetworkBridgeUpdateV1(
|
||||||
NetworkBridgeEvent::PeerMessage(
|
NetworkBridgeEvent::PeerMessage(
|
||||||
peer_a.clone(),
|
peer_a.clone(),
|
||||||
protocol_v1::StatementDistributionMessage::Statement(hash_a, statement.clone()),
|
protocol_v1::StatementDistributionMessage::Statement(hash_a, statement.clone().into()),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}).await;
|
}).await;
|
||||||
@@ -2777,7 +2732,7 @@ mod tests {
|
|||||||
) => {
|
) => {
|
||||||
assert_eq!(recipients, vec![peer_b.clone()]);
|
assert_eq!(recipients, vec![peer_b.clone()]);
|
||||||
assert_eq!(r, hash_a);
|
assert_eq!(r, hash_a);
|
||||||
assert_eq!(s, statement);
|
assert_eq!(s, statement.into());
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await;
|
handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await;
|
||||||
@@ -2951,7 +2906,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let metadata =
|
let metadata =
|
||||||
protocol_v1::StatementDistributionMessage::Statement(hash_a, statement.clone()).get_metadata();
|
protocol_v1::StatementDistributionMessage::Statement(hash_a, statement.clone().into()).get_metadata();
|
||||||
|
|
||||||
handle.send(FromOverseer::Communication {
|
handle.send(FromOverseer::Communication {
|
||||||
msg: StatementDistributionMessage::NetworkBridgeUpdateV1(
|
msg: StatementDistributionMessage::NetworkBridgeUpdateV1(
|
||||||
@@ -3454,7 +3409,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let metadata =
|
let metadata =
|
||||||
protocol_v1::StatementDistributionMessage::Statement(hash_a, statement.clone()).get_metadata();
|
protocol_v1::StatementDistributionMessage::Statement(hash_a, statement.clone().into()).get_metadata();
|
||||||
|
|
||||||
handle.send(FromOverseer::Communication {
|
handle.send(FromOverseer::Communication {
|
||||||
msg: StatementDistributionMessage::Share(hash_a, statement.clone())
|
msg: StatementDistributionMessage::Share(hash_a, statement.clone())
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ pub use sp_consensus_babe::{
|
|||||||
Epoch as BabeEpoch, BabeEpochConfiguration, AllowedSlots as BabeAllowedSlots,
|
Epoch as BabeEpoch, BabeEpochConfiguration, AllowedSlots as BabeAllowedSlots,
|
||||||
};
|
};
|
||||||
|
|
||||||
use polkadot_primitives::v1::{CandidateCommitments, CandidateHash, CollatorPair, CommittedCandidateReceipt, CompactStatement, EncodeAs, Hash, HeadData, Id as ParaId, OutboundHrmpMessage, PersistedValidationData, Signed, UpwardMessage, ValidationCode, BlakeTwo256, HashT, ValidatorIndex};
|
use polkadot_primitives::v1::{BlakeTwo256, CandidateCommitments, CandidateHash, CollatorPair, CommittedCandidateReceipt, CompactStatement, EncodeAs, Hash, HashT, HeadData, Id as ParaId, OutboundHrmpMessage, PersistedValidationData, Signed, UncheckedSigned, UpwardMessage, ValidationCode, ValidatorIndex};
|
||||||
pub use polkadot_parachain::primitives::BlockData;
|
pub use polkadot_parachain::primitives::BlockData;
|
||||||
|
|
||||||
pub mod approval;
|
pub mod approval;
|
||||||
@@ -114,6 +114,9 @@ impl EncodeAs<CompactStatement> for Statement {
|
|||||||
/// Only the compact `SignedStatement` is suitable for submission to the chain.
|
/// Only the compact `SignedStatement` is suitable for submission to the chain.
|
||||||
pub type SignedFullStatement = Signed<Statement, CompactStatement>;
|
pub type SignedFullStatement = Signed<Statement, CompactStatement>;
|
||||||
|
|
||||||
|
/// Variant of `SignedFullStatement` where the signature has not yet been verified.
|
||||||
|
pub type UncheckedSignedFullStatement = UncheckedSigned<Statement, CompactStatement>;
|
||||||
|
|
||||||
/// Candidate invalidity details
|
/// Candidate invalidity details
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum InvalidCandidate {
|
pub enum InvalidCandidate {
|
||||||
|
|||||||
@@ -322,21 +322,6 @@ impl Validator {
|
|||||||
) -> Result<Option<Signed<Payload, RealPayload>>, KeystoreError> {
|
) -> Result<Option<Signed<Payload, RealPayload>>, KeystoreError> {
|
||||||
Signed::sign(&keystore, payload, &self.signing_context, self.index, &self.key).await
|
Signed::sign(&keystore, payload, &self.signing_context, self.index, &self.key).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validate the payload with this validator
|
|
||||||
///
|
|
||||||
/// Validation can only succeed if `signed.validator_index() == self.index()`.
|
|
||||||
/// Normally, this will always be the case for a properly operating program,
|
|
||||||
/// but it's double-checked here anyway.
|
|
||||||
pub fn check_payload<Payload: EncodeAs<RealPayload>, RealPayload: Encode>(
|
|
||||||
&self,
|
|
||||||
signed: Signed<Payload, RealPayload>,
|
|
||||||
) -> Result<(), ()> {
|
|
||||||
if signed.validator_index() != self.index {
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
signed.check_signature(&self.signing_context, &self.id())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AbortOnDrop(future::AbortHandle);
|
struct AbortOnDrop(future::AbortHandle);
|
||||||
|
|||||||
@@ -18,15 +18,18 @@
|
|||||||
|
|
||||||
use lru::LruCache;
|
use lru::LruCache;
|
||||||
|
|
||||||
|
use parity_scale_codec::Encode;
|
||||||
use sp_application_crypto::AppKey;
|
use sp_application_crypto::AppKey;
|
||||||
use sp_core::crypto::Public;
|
use sp_core::crypto::Public;
|
||||||
use sp_keystore::{CryptoStore, SyncCryptoStorePtr};
|
use sp_keystore::{CryptoStore, SyncCryptoStorePtr};
|
||||||
|
|
||||||
use polkadot_primitives::v1::{GroupIndex, Hash, SessionIndex, SessionInfo, ValidatorId, ValidatorIndex};
|
use polkadot_primitives::v1::{CoreState, EncodeAs, GroupIndex, GroupRotationInfo, Hash, OccupiedCore, SessionIndex, SessionInfo, Signed, SigningContext, UncheckedSigned, ValidatorId, ValidatorIndex};
|
||||||
use polkadot_node_subsystem::SubsystemContext;
|
use polkadot_node_subsystem::SubsystemContext;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
request_session_index_for_child, request_session_info,
|
request_session_index_for_child, request_session_info,
|
||||||
|
request_availability_cores,
|
||||||
|
request_validator_groups,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Errors that can happen on runtime fetches.
|
/// Errors that can happen on runtime fetches.
|
||||||
@@ -49,7 +52,7 @@ pub struct RuntimeInfo {
|
|||||||
session_info_cache: LruCache<SessionIndex, ExtendedSessionInfo>,
|
session_info_cache: LruCache<SessionIndex, ExtendedSessionInfo>,
|
||||||
|
|
||||||
/// Key store for determining whether we are a validator and what `ValidatorIndex` we have.
|
/// Key store for determining whether we are a validator and what `ValidatorIndex` we have.
|
||||||
keystore: SyncCryptoStorePtr,
|
keystore: Option<SyncCryptoStorePtr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SessionInfo with additional useful data for validator nodes.
|
/// SessionInfo with additional useful data for validator nodes.
|
||||||
@@ -72,7 +75,7 @@ pub struct ValidatorInfo {
|
|||||||
|
|
||||||
impl RuntimeInfo {
|
impl RuntimeInfo {
|
||||||
/// Create a new `RuntimeInfo` for convenient runtime fetches.
|
/// Create a new `RuntimeInfo` for convenient runtime fetches.
|
||||||
pub fn new(keystore: SyncCryptoStorePtr) -> Self {
|
pub fn new(keystore: Option<SyncCryptoStorePtr>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
// Adjust, depending on how many forks we want to support.
|
// Adjust, depending on how many forks we want to support.
|
||||||
session_index_cache: LruCache::new(10),
|
session_index_cache: LruCache::new(10),
|
||||||
@@ -150,6 +153,23 @@ impl RuntimeInfo {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convenience function for checking the signature of something signed.
|
||||||
|
pub async fn check_signature<Context, Payload, RealPayload>(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut Context,
|
||||||
|
parent: Hash,
|
||||||
|
signed: UncheckedSigned<Payload, RealPayload>,
|
||||||
|
) -> Result<std::result::Result<Signed<Payload, RealPayload>, UncheckedSigned<Payload, RealPayload>>>
|
||||||
|
where
|
||||||
|
Context: SubsystemContext,
|
||||||
|
Payload: EncodeAs<RealPayload> + Clone,
|
||||||
|
RealPayload: Encode + Clone,
|
||||||
|
{
|
||||||
|
let session_index = self.get_session_index(ctx, parent).await?;
|
||||||
|
let info = self.get_session_info_by_index(ctx, parent, session_index).await?;
|
||||||
|
Ok(check_signature(session_index, &info.session_info, parent, signed))
|
||||||
|
}
|
||||||
|
|
||||||
/// Build `ValidatorInfo` for the current session.
|
/// Build `ValidatorInfo` for the current session.
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
@@ -187,8 +207,9 @@ impl RuntimeInfo {
|
|||||||
///
|
///
|
||||||
/// Returns: None if we are not a validator.
|
/// Returns: None if we are not a validator.
|
||||||
async fn get_our_index(&self, validators: &[ValidatorId]) -> Option<ValidatorIndex> {
|
async fn get_our_index(&self, validators: &[ValidatorId]) -> Option<ValidatorIndex> {
|
||||||
|
let keystore = self.keystore.as_ref()?;
|
||||||
for (i, v) in validators.iter().enumerate() {
|
for (i, v) in validators.iter().enumerate() {
|
||||||
if CryptoStore::has_keys(&*self.keystore, &[(v.to_raw_vec(), ValidatorId::ID)])
|
if CryptoStore::has_keys(&**keystore, &[(v.to_raw_vec(), ValidatorId::ID)])
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
return Some(ValidatorIndex(i as u32));
|
return Some(ValidatorIndex(i as u32));
|
||||||
@@ -197,3 +218,69 @@ impl RuntimeInfo {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convenience function for quickly checking the signature on signed data.
|
||||||
|
pub fn check_signature<Payload, RealPayload>(
|
||||||
|
session_index: SessionIndex,
|
||||||
|
session_info: &SessionInfo,
|
||||||
|
relay_parent: Hash,
|
||||||
|
signed: UncheckedSigned<Payload, RealPayload>,
|
||||||
|
) -> std::result::Result<Signed<Payload, RealPayload>, UncheckedSigned<Payload, RealPayload>>
|
||||||
|
where
|
||||||
|
Payload: EncodeAs<RealPayload> + Clone,
|
||||||
|
RealPayload: Encode + Clone,
|
||||||
|
{
|
||||||
|
let signing_context = SigningContext {
|
||||||
|
session_index,
|
||||||
|
parent_hash: relay_parent,
|
||||||
|
};
|
||||||
|
|
||||||
|
session_info.validators
|
||||||
|
.get(signed.unchecked_validator_index().0 as usize)
|
||||||
|
.ok_or_else(|| signed.clone())
|
||||||
|
.and_then(|v| signed.try_into_checked(&signing_context, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Request availability cores from the runtime.
|
||||||
|
pub async fn get_availability_cores<Context>(ctx: &mut Context, relay_parent: Hash)
|
||||||
|
-> Result<Vec<CoreState>>
|
||||||
|
where
|
||||||
|
Context: SubsystemContext,
|
||||||
|
{
|
||||||
|
recv_runtime(request_availability_cores(relay_parent, ctx.sender()).await).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Variant of `request_availability_cores` that only returns occupied ones.
|
||||||
|
pub async fn get_occupied_cores<Context>(
|
||||||
|
ctx: &mut Context,
|
||||||
|
relay_parent: Hash,
|
||||||
|
) -> Result<Vec<OccupiedCore>>
|
||||||
|
where
|
||||||
|
Context: SubsystemContext,
|
||||||
|
{
|
||||||
|
let cores = get_availability_cores(ctx, relay_parent).await?;
|
||||||
|
|
||||||
|
Ok(cores
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|core_state| {
|
||||||
|
if let CoreState::Occupied(occupied) = core_state {
|
||||||
|
Some(occupied)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get group rotation info based on the given relay_parent.
|
||||||
|
pub async fn get_group_rotation_info<Context>(ctx: &mut Context, relay_parent: Hash)
|
||||||
|
-> Result<GroupRotationInfo>
|
||||||
|
where
|
||||||
|
Context: SubsystemContext
|
||||||
|
{
|
||||||
|
// We drop `groups` here as we don't need them, because of `RuntimeInfo`. Ideally we would not
|
||||||
|
// fetch them in the first place.
|
||||||
|
let (_, info) = recv_runtime(request_validator_groups(relay_parent, ctx.sender()).await).await?;
|
||||||
|
Ok(info)
|
||||||
|
}
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ pub enum CollatorProtocolMessage {
|
|||||||
/// Note a collator as having provided a good collation.
|
/// Note a collator as having provided a good collation.
|
||||||
NoteGoodCollation(CollatorId),
|
NoteGoodCollation(CollatorId),
|
||||||
/// Notify a collator that its collation was seconded.
|
/// Notify a collator that its collation was seconded.
|
||||||
NotifyCollationSeconded(CollatorId, SignedFullStatement),
|
NotifyCollationSeconded(CollatorId, Hash, SignedFullStatement),
|
||||||
/// Get a network bridge update.
|
/// Get a network bridge update.
|
||||||
#[from]
|
#[from]
|
||||||
NetworkBridgeUpdateV1(NetworkBridgeEvent<protocol_v1::CollatorProtocolMessage>),
|
NetworkBridgeUpdateV1(NetworkBridgeEvent<protocol_v1::CollatorProtocolMessage>),
|
||||||
|
|||||||
@@ -18,8 +18,6 @@
|
|||||||
//! perspective.
|
//! perspective.
|
||||||
|
|
||||||
use sp_std::prelude::*;
|
use sp_std::prelude::*;
|
||||||
#[cfg(feature = "std")]
|
|
||||||
use sp_std::convert::TryInto;
|
|
||||||
use sp_std::cmp::Ordering;
|
use sp_std::cmp::Ordering;
|
||||||
|
|
||||||
use parity_scale_codec::{Encode, Decode};
|
use parity_scale_codec::{Encode, Decode};
|
||||||
@@ -29,13 +27,9 @@ use serde::{Serialize, Deserialize};
|
|||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use parity_util_mem::{MallocSizeOf, MallocSizeOfOps};
|
use parity_util_mem::{MallocSizeOf, MallocSizeOfOps};
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
use sp_keystore::{CryptoStore, SyncCryptoStorePtr, Error as KeystoreError};
|
|
||||||
use primitives::RuntimeDebug;
|
use primitives::RuntimeDebug;
|
||||||
use runtime_primitives::traits::{AppVerify, Block as BlockT};
|
use runtime_primitives::traits::{AppVerify, Block as BlockT};
|
||||||
use inherents::InherentIdentifier;
|
use inherents::InherentIdentifier;
|
||||||
#[cfg(feature = "std")]
|
|
||||||
use application_crypto::AppKey;
|
|
||||||
use application_crypto::KeyTypeId;
|
use application_crypto::KeyTypeId;
|
||||||
|
|
||||||
pub use runtime_primitives::traits::{BlakeTwo256, Hash as HashT, Verify, IdentifyAccount};
|
pub use runtime_primitives::traits::{BlakeTwo256, Hash as HashT, Verify, IdentifyAccount};
|
||||||
@@ -731,9 +725,6 @@ impl CompactStatement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A signed compact statement, suitable to be sent to the chain.
|
|
||||||
pub type SignedStatement = Signed<CompactStatement>;
|
|
||||||
|
|
||||||
/// An either implicit or explicit attestation to the validity of a parachain
|
/// An either implicit or explicit attestation to the validity of a parachain
|
||||||
/// candidate.
|
/// candidate.
|
||||||
#[derive(Clone, Eq, PartialEq, Decode, Encode, RuntimeDebug)]
|
#[derive(Clone, Eq, PartialEq, Decode, Encode, RuntimeDebug)]
|
||||||
@@ -866,156 +857,6 @@ pub mod id {
|
|||||||
pub const PARACHAIN_HOST: ApiId = *b"parahost";
|
pub const PARACHAIN_HOST: ApiId = *b"parahost";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This helper trait ensures that we can encode Statement as CompactStatement,
|
|
||||||
/// and anything as itself.
|
|
||||||
///
|
|
||||||
/// This resembles `parity_scale_codec::EncodeLike`, but it's distinct:
|
|
||||||
/// EncodeLike is a marker trait which asserts at the typesystem level that
|
|
||||||
/// one type's encoding is a valid encoding for another type. It doesn't
|
|
||||||
/// perform any type conversion when encoding.
|
|
||||||
///
|
|
||||||
/// This trait, on the other hand, provides a method which can be used to
|
|
||||||
/// simultaneously convert and encode one type as another.
|
|
||||||
pub trait EncodeAs<T> {
|
|
||||||
/// Convert Self into T, then encode T.
|
|
||||||
///
|
|
||||||
/// This is useful when T is a subset of Self, reducing encoding costs;
|
|
||||||
/// its signature also means that we do not need to clone Self in order
|
|
||||||
/// to retain ownership, as we would if we were to do
|
|
||||||
/// `self.clone().into().encode()`.
|
|
||||||
fn encode_as(&self) -> Vec<u8>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Encode> EncodeAs<T> for T {
|
|
||||||
fn encode_as(&self) -> Vec<u8> {
|
|
||||||
self.encode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A signed type which encapsulates the common desire to sign some data and validate a signature.
|
|
||||||
///
|
|
||||||
/// Note that the internal fields are not public; they are all accessable by immutable getters.
|
|
||||||
/// This reduces the chance that they are accidentally mutated, invalidating the signature.
|
|
||||||
#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)]
|
|
||||||
pub struct Signed<Payload, RealPayload = Payload> {
|
|
||||||
/// The payload is part of the signed data. The rest is the signing context,
|
|
||||||
/// which is known both at signing and at validation.
|
|
||||||
payload: Payload,
|
|
||||||
/// The index of the validator signing this statement.
|
|
||||||
validator_index: ValidatorIndex,
|
|
||||||
/// The signature by the validator of the signed payload.
|
|
||||||
signature: ValidatorSignature,
|
|
||||||
/// This ensures the real payload is tracked at the typesystem level.
|
|
||||||
real_payload: sp_std::marker::PhantomData<RealPayload>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can't bound this on `Payload: Into<RealPayload>` beacuse that conversion consumes
|
|
||||||
// the payload, and we don't want that. We can't bound it on `Payload: AsRef<RealPayload>`
|
|
||||||
// because there's no blanket impl of `AsRef<T> for T`. In the end, we just invent our
|
|
||||||
// own trait which does what we need: EncodeAs.
|
|
||||||
impl<Payload: EncodeAs<RealPayload>, RealPayload: Encode> Signed<Payload, RealPayload> {
|
|
||||||
fn payload_data<H: Encode>(payload: &Payload, context: &SigningContext<H>) -> Vec<u8> {
|
|
||||||
// equivalent to (real_payload, context).encode()
|
|
||||||
let mut out = payload.encode_as();
|
|
||||||
out.extend(context.encode());
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used to create a `Signed` from already existing parts.
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
pub fn new<H: Encode>(
|
|
||||||
payload: Payload,
|
|
||||||
validator_index: ValidatorIndex,
|
|
||||||
signature: ValidatorSignature,
|
|
||||||
context: &SigningContext<H>,
|
|
||||||
key: &ValidatorId,
|
|
||||||
) -> Option<Self> {
|
|
||||||
let s = Self {
|
|
||||||
payload,
|
|
||||||
validator_index,
|
|
||||||
signature,
|
|
||||||
real_payload: std::marker::PhantomData,
|
|
||||||
};
|
|
||||||
|
|
||||||
s.check_signature(context, key).ok()?;
|
|
||||||
|
|
||||||
Some(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sign this payload with the given context and key, storing the validator index.
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
pub async fn sign<H: Encode>(
|
|
||||||
keystore: &SyncCryptoStorePtr,
|
|
||||||
payload: Payload,
|
|
||||||
context: &SigningContext<H>,
|
|
||||||
validator_index: ValidatorIndex,
|
|
||||||
key: &ValidatorId,
|
|
||||||
) -> Result<Option<Self>, KeystoreError> {
|
|
||||||
let data = Self::payload_data(&payload, context);
|
|
||||||
let signature = CryptoStore::sign_with(
|
|
||||||
&**keystore,
|
|
||||||
ValidatorId::ID,
|
|
||||||
&key.into(),
|
|
||||||
&data,
|
|
||||||
).await?;
|
|
||||||
|
|
||||||
let signature = match signature {
|
|
||||||
Some(sig) => sig.try_into().map_err(|_| KeystoreError::KeyNotSupported(ValidatorId::ID))?,
|
|
||||||
None => return Ok(None),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Some(Self {
|
|
||||||
payload,
|
|
||||||
validator_index,
|
|
||||||
signature,
|
|
||||||
real_payload: std::marker::PhantomData,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Validate the payload given the context and public key.
|
|
||||||
pub fn check_signature<H: Encode>(&self, context: &SigningContext<H>, key: &ValidatorId) -> Result<(), ()> {
|
|
||||||
let data = Self::payload_data(&self.payload, context);
|
|
||||||
if self.signature.verify(data.as_slice(), key) { Ok(()) } else { Err(()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Immutably access the payload.
|
|
||||||
#[inline]
|
|
||||||
pub fn payload(&self) -> &Payload {
|
|
||||||
&self.payload
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Immutably access the validator index.
|
|
||||||
#[inline]
|
|
||||||
pub fn validator_index(&self) -> ValidatorIndex {
|
|
||||||
self.validator_index
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Immutably access the signature.
|
|
||||||
#[inline]
|
|
||||||
pub fn signature(&self) -> &ValidatorSignature {
|
|
||||||
&self.signature
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Discard signing data, get the payload
|
|
||||||
// Note: can't `impl<P, R> From<Signed<P, R>> for P` because the orphan rule exception doesn't
|
|
||||||
// handle this case yet. Likewise can't `impl<P, R> Into<P> for Signed<P, R>` because it might
|
|
||||||
// potentially conflict with the global blanket impl, even though it currently doesn't.
|
|
||||||
#[inline]
|
|
||||||
pub fn into_payload(self) -> Payload {
|
|
||||||
self.payload
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert `Payload` into `RealPayload`.
|
|
||||||
pub fn convert_payload(&self) -> Signed<RealPayload> where for<'a> &'a Payload: Into<RealPayload> {
|
|
||||||
Signed {
|
|
||||||
signature: self.signature.clone(),
|
|
||||||
validator_index: self.validator_index,
|
|
||||||
payload: self.payload().into(),
|
|
||||||
real_payload: sp_std::marker::PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Custom validity errors used in Polkadot while validating transactions.
|
/// Custom validity errors used in Polkadot while validating transactions.
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum ValidityError {
|
pub enum ValidityError {
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ pub use polkadot_parachain::primitives::{
|
|||||||
// Export some basic parachain primitives from v0.
|
// Export some basic parachain primitives from v0.
|
||||||
pub use crate::v0::{
|
pub use crate::v0::{
|
||||||
CollatorId, CollatorSignature, PARACHAIN_KEY_TYPE_ID, ValidatorId, ValidatorIndex,
|
CollatorId, CollatorSignature, PARACHAIN_KEY_TYPE_ID, ValidatorId, ValidatorIndex,
|
||||||
ValidatorSignature, SigningContext, Signed, ValidityAttestation,
|
ValidatorSignature, SigningContext, ValidityAttestation,
|
||||||
CompactStatement, SignedStatement, EncodeAs,
|
CompactStatement,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
@@ -58,6 +58,10 @@ pub use crate::v0::{ValidatorPair, CollatorPair};
|
|||||||
pub use sp_staking::SessionIndex;
|
pub use sp_staking::SessionIndex;
|
||||||
pub use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
|
pub use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
|
||||||
|
|
||||||
|
/// Signed data.
|
||||||
|
mod signed;
|
||||||
|
pub use signed::{Signed, UncheckedSigned, EncodeAs};
|
||||||
|
|
||||||
/// A declarations of storage keys where an external observer can find some interesting data.
|
/// A declarations of storage keys where an external observer can find some interesting data.
|
||||||
pub mod well_known_keys {
|
pub mod well_known_keys {
|
||||||
use super::{Id, HrmpChannelId};
|
use super::{Id, HrmpChannelId};
|
||||||
@@ -169,6 +173,7 @@ pub mod well_known_keys {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Unique identifier for the Parachains Inherent
|
/// Unique identifier for the Parachains Inherent
|
||||||
pub const PARACHAINS_INHERENT_IDENTIFIER: InherentIdentifier = *b"parachn0";
|
pub const PARACHAINS_INHERENT_IDENTIFIER: InherentIdentifier = *b"parachn0";
|
||||||
|
|
||||||
@@ -461,11 +466,19 @@ impl From<BitVec<bitvec::order::Lsb0, u8>> for AvailabilityBitfield {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// A signed compact statement, suitable to be sent to the chain.
|
||||||
|
pub type SignedStatement = Signed<CompactStatement>;
|
||||||
|
|
||||||
/// A bitfield signed by a particular validator about the availability of pending candidates.
|
/// A bitfield signed by a particular validator about the availability of pending candidates.
|
||||||
pub type SignedAvailabilityBitfield = Signed<AvailabilityBitfield>;
|
pub type SignedAvailabilityBitfield = Signed<AvailabilityBitfield>;
|
||||||
|
/// A signed bitfield with signature not yet checked.
|
||||||
|
pub type UncheckedSignedAvailabilityBitfield = UncheckedSigned<AvailabilityBitfield>;
|
||||||
|
|
||||||
/// A set of signed availability bitfields. Should be sorted by validator index, ascending.
|
/// A set of signed availability bitfields. Should be sorted by validator index, ascending.
|
||||||
pub type SignedAvailabilityBitfields = Vec<SignedAvailabilityBitfield>;
|
pub type SignedAvailabilityBitfields = Vec<SignedAvailabilityBitfield>;
|
||||||
|
/// A set of unchecked signed availability bitfields. Should be sorted by validator index, ascending.
|
||||||
|
pub type UncheckedSignedAvailabilityBitfields = Vec<UncheckedSignedAvailabilityBitfield>;
|
||||||
|
|
||||||
/// A backed (or backable, depending on context) candidate.
|
/// A backed (or backable, depending on context) candidate.
|
||||||
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
|
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
|
||||||
@@ -1120,7 +1133,7 @@ pub struct DisputeState<N = BlockNumber> {
|
|||||||
#[derive(Encode, Decode, Clone, PartialEq, RuntimeDebug)]
|
#[derive(Encode, Decode, Clone, PartialEq, RuntimeDebug)]
|
||||||
pub struct InherentData<HDR: HeaderT = Header> {
|
pub struct InherentData<HDR: HeaderT = Header> {
|
||||||
/// Signed bitfields by validators about availability.
|
/// Signed bitfields by validators about availability.
|
||||||
pub bitfields: SignedAvailabilityBitfields,
|
pub bitfields: UncheckedSignedAvailabilityBitfields,
|
||||||
/// Backed candidates for inclusion in the block.
|
/// Backed candidates for inclusion in the block.
|
||||||
pub backed_candidates: Vec<BackedCandidate<HDR::Hash>>,
|
pub backed_candidates: Vec<BackedCandidate<HDR::Hash>>,
|
||||||
/// Sets of dispute votes for inclusion,
|
/// Sets of dispute votes for inclusion,
|
||||||
@@ -0,0 +1,282 @@
|
|||||||
|
// Copyright 2021 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 parity_scale_codec::{Encode, Decode};
|
||||||
|
|
||||||
|
use sp_std::prelude::Vec;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
use sp_std::convert::TryInto;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
use application_crypto::AppKey;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
use sp_keystore::{CryptoStore, SyncCryptoStorePtr, Error as KeystoreError};
|
||||||
|
|
||||||
|
use primitives::RuntimeDebug;
|
||||||
|
use runtime_primitives::traits::AppVerify;
|
||||||
|
|
||||||
|
use crate::v0::{SigningContext, ValidatorId, ValidatorSignature, ValidatorIndex};
|
||||||
|
|
||||||
|
/// Signed data with signature already verified.
|
||||||
|
///
|
||||||
|
/// NOTE: This type does not have an Encode/Decode instance, as this would cancel out our
|
||||||
|
/// valid signature guarantees. If you need to encode/decode you have to convert into an
|
||||||
|
/// `UncheckedSigned` first.
|
||||||
|
///
|
||||||
|
/// `Signed` can easily be converted into `UncheckedSigned` and conversion back via `into_signed`
|
||||||
|
/// enforces a valid signature again.
|
||||||
|
#[derive(Clone, PartialEq, Eq, RuntimeDebug)]
|
||||||
|
pub struct Signed<Payload, RealPayload = Payload>(UncheckedSigned<Payload, RealPayload>);
|
||||||
|
|
||||||
|
/// Unchecked signed data, can be converted to `Signed` by checking the signature.
|
||||||
|
#[derive(Clone, PartialEq, Eq, RuntimeDebug, Encode, Decode)]
|
||||||
|
pub struct UncheckedSigned<Payload, RealPayload = Payload> {
|
||||||
|
/// The payload is part of the signed data. The rest is the signing context,
|
||||||
|
/// which is known both at signing and at validation.
|
||||||
|
payload: Payload,
|
||||||
|
/// The index of the validator signing this statement.
|
||||||
|
validator_index: ValidatorIndex,
|
||||||
|
/// The signature by the validator of the signed payload.
|
||||||
|
signature: ValidatorSignature,
|
||||||
|
/// This ensures the real payload is tracked at the typesystem level.
|
||||||
|
real_payload: sp_std::marker::PhantomData<RealPayload>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Payload: EncodeAs<RealPayload>, RealPayload: Encode> Signed<Payload, RealPayload> {
|
||||||
|
/// Used to create a `Signed` from already existing parts.
|
||||||
|
///
|
||||||
|
/// The signature is checked as part of the process.
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub fn new<H: Encode>(
|
||||||
|
payload: Payload,
|
||||||
|
validator_index: ValidatorIndex,
|
||||||
|
signature: ValidatorSignature,
|
||||||
|
context: &SigningContext<H>,
|
||||||
|
key: &ValidatorId,
|
||||||
|
) -> Option<Self> {
|
||||||
|
let s = UncheckedSigned {
|
||||||
|
payload,
|
||||||
|
validator_index,
|
||||||
|
signature,
|
||||||
|
real_payload: std::marker::PhantomData,
|
||||||
|
};
|
||||||
|
|
||||||
|
s.check_signature(context, key).ok()?;
|
||||||
|
|
||||||
|
Some(Self(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new `Signed` by signing data.
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub async fn sign<H: Encode>(
|
||||||
|
keystore: &SyncCryptoStorePtr,
|
||||||
|
payload: Payload,
|
||||||
|
context: &SigningContext<H>,
|
||||||
|
validator_index: ValidatorIndex,
|
||||||
|
key: &ValidatorId,
|
||||||
|
) -> Result<Option<Self>, KeystoreError> {
|
||||||
|
let r = UncheckedSigned::sign(keystore, payload, context, validator_index, key).await?;
|
||||||
|
Ok(r.map(Self))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to convert from `UncheckedSigned` by checking the signature.
|
||||||
|
pub fn try_from_unchecked<H: Encode>(
|
||||||
|
unchecked: UncheckedSigned<Payload, RealPayload>,
|
||||||
|
context: &SigningContext<H>,
|
||||||
|
key: &ValidatorId
|
||||||
|
) -> Result<Self, UncheckedSigned<Payload, RealPayload>> {
|
||||||
|
if unchecked.check_signature(context, key).is_ok() {
|
||||||
|
Ok(Self(unchecked))
|
||||||
|
} else {
|
||||||
|
Err(unchecked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reference to data as unchecked.
|
||||||
|
pub fn as_unchecked(&self) -> &UncheckedSigned<Payload, RealPayload> {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Immutably access the payload.
|
||||||
|
#[inline]
|
||||||
|
pub fn payload(&self) -> &Payload {
|
||||||
|
&self.0.payload
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Immutably access the validator index.
|
||||||
|
#[inline]
|
||||||
|
pub fn validator_index(&self) -> ValidatorIndex {
|
||||||
|
self.0.validator_index
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Immutably access the signature.
|
||||||
|
#[inline]
|
||||||
|
pub fn signature(&self) -> &ValidatorSignature {
|
||||||
|
&self.0.signature
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Discard signing data, get the payload
|
||||||
|
#[inline]
|
||||||
|
pub fn into_payload(self) -> Payload {
|
||||||
|
self.0.payload
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert `Payload` into `RealPayload`.
|
||||||
|
pub fn convert_payload(&self) -> Signed<RealPayload> where for<'a> &'a Payload: Into<RealPayload> {
|
||||||
|
Signed(self.0.unchecked_convert_payload())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can't bound this on `Payload: Into<RealPayload>` beacuse that conversion consumes
|
||||||
|
// the payload, and we don't want that. We can't bound it on `Payload: AsRef<RealPayload>`
|
||||||
|
// because there's no blanket impl of `AsRef<T> for T`. In the end, we just invent our
|
||||||
|
// own trait which does what we need: EncodeAs.
|
||||||
|
impl<Payload: EncodeAs<RealPayload>, RealPayload: Encode> UncheckedSigned<Payload, RealPayload> {
|
||||||
|
/// Used to create a `UncheckedSigned` from already existing parts.
|
||||||
|
///
|
||||||
|
/// Signature is not checked here, hence `UncheckedSigned`.
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub fn new(
|
||||||
|
payload: Payload,
|
||||||
|
validator_index: ValidatorIndex,
|
||||||
|
signature: ValidatorSignature,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
payload,
|
||||||
|
validator_index,
|
||||||
|
signature,
|
||||||
|
real_payload: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check signature and convert to `Signed` if successful.
|
||||||
|
pub fn try_into_checked<H: Encode>(
|
||||||
|
self,
|
||||||
|
context: &SigningContext<H>,
|
||||||
|
key: &ValidatorId
|
||||||
|
) -> Result<Signed<Payload, RealPayload>, Self> {
|
||||||
|
Signed::try_from_unchecked(self, context, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Immutably access the payload.
|
||||||
|
#[inline]
|
||||||
|
pub fn unchecked_payload(&self) -> &Payload {
|
||||||
|
&self.payload
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Immutably access the validator index.
|
||||||
|
#[inline]
|
||||||
|
pub fn unchecked_validator_index(&self) -> ValidatorIndex {
|
||||||
|
self.validator_index
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Immutably access the signature.
|
||||||
|
#[inline]
|
||||||
|
pub fn unchecked_signature(&self) -> &ValidatorSignature {
|
||||||
|
&self.signature
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Discard signing data, get the payload
|
||||||
|
#[inline]
|
||||||
|
pub fn unchecked_into_payload(self) -> Payload {
|
||||||
|
self.payload
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert `Payload` into `RealPayload`.
|
||||||
|
pub fn unchecked_convert_payload(&self) -> UncheckedSigned<RealPayload> where for<'a> &'a Payload: Into<RealPayload> {
|
||||||
|
UncheckedSigned {
|
||||||
|
signature: self.signature.clone(),
|
||||||
|
validator_index: self.validator_index,
|
||||||
|
payload: (&self.payload).into(),
|
||||||
|
real_payload: sp_std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn payload_data<H: Encode>(payload: &Payload, context: &SigningContext<H>) -> Vec<u8> {
|
||||||
|
// equivalent to (real_payload, context).encode()
|
||||||
|
let mut out = payload.encode_as();
|
||||||
|
out.extend(context.encode());
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sign this payload with the given context and key, storing the validator index.
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
async fn sign<H: Encode>(
|
||||||
|
keystore: &SyncCryptoStorePtr,
|
||||||
|
payload: Payload,
|
||||||
|
context: &SigningContext<H>,
|
||||||
|
validator_index: ValidatorIndex,
|
||||||
|
key: &ValidatorId,
|
||||||
|
) -> Result<Option<Self>, KeystoreError> {
|
||||||
|
let data = Self::payload_data(&payload, context);
|
||||||
|
let signature = CryptoStore::sign_with(
|
||||||
|
&**keystore,
|
||||||
|
ValidatorId::ID,
|
||||||
|
&key.into(),
|
||||||
|
&data,
|
||||||
|
).await?;
|
||||||
|
|
||||||
|
let signature = match signature {
|
||||||
|
Some(sig) => sig.try_into().map_err(|_| KeystoreError::KeyNotSupported(ValidatorId::ID))?,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Some(Self {
|
||||||
|
payload,
|
||||||
|
validator_index,
|
||||||
|
signature,
|
||||||
|
real_payload: std::marker::PhantomData,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validate the payload given the context and public key.
|
||||||
|
fn check_signature<H: Encode>(&self, context: &SigningContext<H>, key: &ValidatorId) -> Result<(), ()> {
|
||||||
|
let data = Self::payload_data(&self.payload, context);
|
||||||
|
if self.signature.verify(data.as_slice(), key) { Ok(()) } else { Err(()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Payload, RealPayload> From<Signed<Payload, RealPayload>> for UncheckedSigned<Payload, RealPayload> {
|
||||||
|
fn from(signed: Signed<Payload, RealPayload>) -> Self {
|
||||||
|
signed.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This helper trait ensures that we can encode Statement as CompactStatement,
|
||||||
|
/// and anything as itself.
|
||||||
|
///
|
||||||
|
/// This resembles `parity_scale_codec::EncodeLike`, but it's distinct:
|
||||||
|
/// EncodeLike is a marker trait which asserts at the typesystem level that
|
||||||
|
/// one type's encoding is a valid encoding for another type. It doesn't
|
||||||
|
/// perform any type conversion when encoding.
|
||||||
|
///
|
||||||
|
/// This trait, on the other hand, provides a method which can be used to
|
||||||
|
/// simultaneously convert and encode one type as another.
|
||||||
|
pub trait EncodeAs<T> {
|
||||||
|
/// Convert Self into T, then encode T.
|
||||||
|
///
|
||||||
|
/// This is useful when T is a subset of Self, reducing encoding costs;
|
||||||
|
/// its signature also means that we do not need to clone Self in order
|
||||||
|
/// to retain ownership, as we would if we were to do
|
||||||
|
/// `self.clone().into().encode()`.
|
||||||
|
fn encode_as(&self) -> Vec<u8>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Encode> EncodeAs<T> for T {
|
||||||
|
fn encode_as(&self) -> Vec<u8> {
|
||||||
|
self.encode()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -334,7 +334,7 @@ enum CollatorProtocolMessage {
|
|||||||
/// Note a collator as having provided a good collation.
|
/// Note a collator as having provided a good collation.
|
||||||
NoteGoodCollation(CollatorId, SignedFullStatement),
|
NoteGoodCollation(CollatorId, SignedFullStatement),
|
||||||
/// Notify a collator that its collation was seconded.
|
/// Notify a collator that its collation was seconded.
|
||||||
NotifyCollationSeconded(CollatorId, SignedFullStatement),
|
NotifyCollationSeconded(CollatorId, Hash, SignedFullStatement),
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
use sp_std::prelude::*;
|
use sp_std::prelude::*;
|
||||||
use primitives::v1::{
|
use primitives::v1::{
|
||||||
CandidateCommitments, CandidateDescriptor, ValidatorIndex, Id as ParaId,
|
CandidateCommitments, CandidateDescriptor, ValidatorIndex, Id as ParaId,
|
||||||
AvailabilityBitfield as AvailabilityBitfield, SignedAvailabilityBitfields, SigningContext,
|
AvailabilityBitfield as AvailabilityBitfield, UncheckedSignedAvailabilityBitfields, SigningContext,
|
||||||
BackedCandidate, CoreIndex, GroupIndex, CommittedCandidateReceipt,
|
BackedCandidate, CoreIndex, GroupIndex, CommittedCandidateReceipt,
|
||||||
CandidateReceipt, HeadData, CandidateHash,
|
CandidateReceipt, HeadData, CandidateHash,
|
||||||
};
|
};
|
||||||
@@ -236,7 +236,7 @@ impl<T: Config> Module<T> {
|
|||||||
/// becoming available.
|
/// becoming available.
|
||||||
pub(crate) fn process_bitfields(
|
pub(crate) fn process_bitfields(
|
||||||
expected_bits: usize,
|
expected_bits: usize,
|
||||||
signed_bitfields: SignedAvailabilityBitfields,
|
unchecked_bitfields: UncheckedSignedAvailabilityBitfields,
|
||||||
core_lookup: impl Fn(CoreIndex) -> Option<ParaId>,
|
core_lookup: impl Fn(CoreIndex) -> Option<ParaId>,
|
||||||
) -> Result<Vec<CoreIndex>, DispatchError> {
|
) -> Result<Vec<CoreIndex>, DispatchError> {
|
||||||
let validators = shared::Module::<T>::active_validator_keys();
|
let validators = shared::Module::<T>::active_validator_keys();
|
||||||
@@ -247,12 +247,13 @@ impl<T: Config> Module<T> {
|
|||||||
.map(|core_para| core_para.map(|p| (p, PendingAvailability::<T>::get(&p))))
|
.map(|core_para| core_para.map(|p| (p, PendingAvailability::<T>::get(&p))))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|
||||||
// do sanity checks on the bitfields:
|
// do sanity checks on the bitfields:
|
||||||
// 1. no more than one bitfield per validator
|
// 1. no more than one bitfield per validator
|
||||||
// 2. bitfields are ascending by validator index.
|
// 2. bitfields are ascending by validator index.
|
||||||
// 3. each bitfield has exactly `expected_bits`
|
// 3. each bitfield has exactly `expected_bits`
|
||||||
// 4. signature is valid.
|
// 4. signature is valid.
|
||||||
{
|
let signed_bitfields = {
|
||||||
let occupied_bitmask: BitVec<BitOrderLsb0, u8> = assigned_paras_record.iter()
|
let occupied_bitmask: BitVec<BitOrderLsb0, u8> = assigned_paras_record.iter()
|
||||||
.map(|p| p.as_ref()
|
.map(|p| p.as_ref()
|
||||||
.map_or(false, |(_id, pending_availability)| pending_availability.is_some())
|
.map_or(false, |(_id, pending_availability)| pending_availability.is_some())
|
||||||
@@ -266,37 +267,42 @@ impl<T: Config> Module<T> {
|
|||||||
session_index,
|
session_index,
|
||||||
};
|
};
|
||||||
|
|
||||||
for signed_bitfield in &signed_bitfields {
|
let mut signed_bitfields = Vec::with_capacity(unchecked_bitfields.len());
|
||||||
|
|
||||||
|
for unchecked_bitfield in unchecked_bitfields {
|
||||||
ensure!(
|
ensure!(
|
||||||
signed_bitfield.payload().0.len() == expected_bits,
|
unchecked_bitfield.unchecked_payload().0.len() == expected_bits,
|
||||||
Error::<T>::WrongBitfieldSize,
|
Error::<T>::WrongBitfieldSize,
|
||||||
);
|
);
|
||||||
|
|
||||||
ensure!(
|
ensure!(
|
||||||
last_index.map_or(true, |last| last < signed_bitfield.validator_index()),
|
last_index.map_or(true, |last| last < unchecked_bitfield.unchecked_validator_index()),
|
||||||
Error::<T>::BitfieldDuplicateOrUnordered,
|
Error::<T>::BitfieldDuplicateOrUnordered,
|
||||||
);
|
);
|
||||||
|
|
||||||
ensure!(
|
ensure!(
|
||||||
(signed_bitfield.validator_index().0 as usize) < validators.len(),
|
(unchecked_bitfield.unchecked_validator_index().0 as usize) < validators.len(),
|
||||||
Error::<T>::ValidatorIndexOutOfBounds,
|
Error::<T>::ValidatorIndexOutOfBounds,
|
||||||
);
|
);
|
||||||
|
|
||||||
ensure!(
|
ensure!(
|
||||||
occupied_bitmask.clone() & signed_bitfield.payload().0.clone() == signed_bitfield.payload().0,
|
occupied_bitmask.clone() & unchecked_bitfield.unchecked_payload().0.clone() == unchecked_bitfield.unchecked_payload().0,
|
||||||
Error::<T>::UnoccupiedBitInBitfield,
|
Error::<T>::UnoccupiedBitInBitfield,
|
||||||
);
|
);
|
||||||
|
|
||||||
let validator_public = &validators[signed_bitfield.validator_index().0 as usize];
|
let validator_public = &validators[unchecked_bitfield.unchecked_validator_index().0 as usize];
|
||||||
|
|
||||||
signed_bitfield.check_signature(
|
last_index = Some(unchecked_bitfield.unchecked_validator_index());
|
||||||
|
|
||||||
|
signed_bitfields.push(
|
||||||
|
unchecked_bitfield.try_into_checked(
|
||||||
&signing_context,
|
&signing_context,
|
||||||
validator_public,
|
validator_public,
|
||||||
).map_err(|_| Error::<T>::InvalidBitfieldSignature)?;
|
).map_err(|_| Error::<T>::InvalidBitfieldSignature)?
|
||||||
|
);
|
||||||
last_index = Some(signed_bitfield.validator_index());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
signed_bitfields
|
||||||
|
};
|
||||||
|
|
||||||
let now = <frame_system::Pallet<T>>::block_number();
|
let now = <frame_system::Pallet<T>>::block_number();
|
||||||
for signed_bitfield in signed_bitfields {
|
for signed_bitfield in signed_bitfields {
|
||||||
@@ -902,7 +908,7 @@ mod tests {
|
|||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use futures::executor::block_on;
|
use futures::executor::block_on;
|
||||||
use primitives::v0::PARACHAIN_KEY_TYPE_ID;
|
use primitives::{v0::PARACHAIN_KEY_TYPE_ID, v1::UncheckedSignedAvailabilityBitfield};
|
||||||
use primitives::v1::{BlockNumber, Hash};
|
use primitives::v1::{BlockNumber, Hash};
|
||||||
use primitives::v1::{
|
use primitives::v1::{
|
||||||
SignedAvailabilityBitfield, CompactStatement as Statement, ValidityAttestation, CollatorId,
|
SignedAvailabilityBitfield, CompactStatement as Statement, ValidityAttestation, CollatorId,
|
||||||
@@ -1257,7 +1263,7 @@ mod tests {
|
|||||||
|
|
||||||
assert!(Inclusion::process_bitfields(
|
assert!(Inclusion::process_bitfields(
|
||||||
expected_bits(),
|
expected_bits(),
|
||||||
vec![signed],
|
vec![signed.into()],
|
||||||
&core_lookup,
|
&core_lookup,
|
||||||
).is_err());
|
).is_err());
|
||||||
}
|
}
|
||||||
@@ -1275,7 +1281,7 @@ mod tests {
|
|||||||
|
|
||||||
assert!(Inclusion::process_bitfields(
|
assert!(Inclusion::process_bitfields(
|
||||||
expected_bits() + 1,
|
expected_bits() + 1,
|
||||||
vec![signed],
|
vec![signed.into()],
|
||||||
&core_lookup,
|
&core_lookup,
|
||||||
).is_err());
|
).is_err());
|
||||||
}
|
}
|
||||||
@@ -1283,13 +1289,14 @@ mod tests {
|
|||||||
// duplicate.
|
// duplicate.
|
||||||
{
|
{
|
||||||
let bare_bitfield = default_bitfield();
|
let bare_bitfield = default_bitfield();
|
||||||
let signed = block_on(sign_bitfield(
|
let signed: UncheckedSignedAvailabilityBitfield =
|
||||||
|
block_on(sign_bitfield(
|
||||||
&keystore,
|
&keystore,
|
||||||
&validators[0],
|
&validators[0],
|
||||||
ValidatorIndex(0),
|
ValidatorIndex(0),
|
||||||
bare_bitfield,
|
bare_bitfield,
|
||||||
&signing_context,
|
&signing_context,
|
||||||
));
|
)).into();
|
||||||
|
|
||||||
assert!(Inclusion::process_bitfields(
|
assert!(Inclusion::process_bitfields(
|
||||||
expected_bits(),
|
expected_bits(),
|
||||||
@@ -1307,7 +1314,7 @@ mod tests {
|
|||||||
ValidatorIndex(0),
|
ValidatorIndex(0),
|
||||||
bare_bitfield.clone(),
|
bare_bitfield.clone(),
|
||||||
&signing_context,
|
&signing_context,
|
||||||
));
|
)).into();
|
||||||
|
|
||||||
let signed_1 = block_on(sign_bitfield(
|
let signed_1 = block_on(sign_bitfield(
|
||||||
&keystore,
|
&keystore,
|
||||||
@@ -1315,7 +1322,7 @@ mod tests {
|
|||||||
ValidatorIndex(1),
|
ValidatorIndex(1),
|
||||||
bare_bitfield,
|
bare_bitfield,
|
||||||
&signing_context,
|
&signing_context,
|
||||||
));
|
)).into();
|
||||||
|
|
||||||
assert!(Inclusion::process_bitfields(
|
assert!(Inclusion::process_bitfields(
|
||||||
expected_bits(),
|
expected_bits(),
|
||||||
@@ -1338,7 +1345,7 @@ mod tests {
|
|||||||
|
|
||||||
assert!(Inclusion::process_bitfields(
|
assert!(Inclusion::process_bitfields(
|
||||||
expected_bits(),
|
expected_bits(),
|
||||||
vec![signed],
|
vec![signed.into()],
|
||||||
&core_lookup,
|
&core_lookup,
|
||||||
).is_err());
|
).is_err());
|
||||||
}
|
}
|
||||||
@@ -1356,7 +1363,7 @@ mod tests {
|
|||||||
|
|
||||||
assert!(Inclusion::process_bitfields(
|
assert!(Inclusion::process_bitfields(
|
||||||
expected_bits(),
|
expected_bits(),
|
||||||
vec![signed],
|
vec![signed.into()],
|
||||||
&core_lookup,
|
&core_lookup,
|
||||||
).is_ok());
|
).is_ok());
|
||||||
}
|
}
|
||||||
@@ -1391,7 +1398,7 @@ mod tests {
|
|||||||
|
|
||||||
assert!(Inclusion::process_bitfields(
|
assert!(Inclusion::process_bitfields(
|
||||||
expected_bits(),
|
expected_bits(),
|
||||||
vec![signed],
|
vec![signed.into()],
|
||||||
&core_lookup,
|
&core_lookup,
|
||||||
).is_ok());
|
).is_ok());
|
||||||
|
|
||||||
@@ -1430,7 +1437,7 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
Inclusion::process_bitfields(
|
Inclusion::process_bitfields(
|
||||||
expected_bits(),
|
expected_bits(),
|
||||||
vec![signed],
|
vec![signed.into()],
|
||||||
&core_lookup,
|
&core_lookup,
|
||||||
),
|
),
|
||||||
Ok(vec![]),
|
Ok(vec![]),
|
||||||
@@ -1549,7 +1556,7 @@ mod tests {
|
|||||||
ValidatorIndex(i as _),
|
ValidatorIndex(i as _),
|
||||||
to_sign,
|
to_sign,
|
||||||
&signing_context,
|
&signing_context,
|
||||||
)))
|
)).into())
|
||||||
}).collect();
|
}).collect();
|
||||||
|
|
||||||
assert!(Inclusion::process_bitfields(
|
assert!(Inclusion::process_bitfields(
|
||||||
|
|||||||
Reference in New Issue
Block a user