mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-08-07 03:05:46 +00:00
cargo +nightly fmt (#3540)
* cargo +nightly fmt * add cargo-fmt check to ci * update ci * fmt * fmt * skip macro * ignore bridges
This commit is contained in:
@@ -19,17 +19,21 @@
|
||||
//! Provides the [`AbstractClient`] trait that is a super trait that combines all the traits the client implements.
|
||||
//! There is also the [`Client`] enum that combines all the different clients into one common structure.
|
||||
|
||||
use std::sync::Arc;
|
||||
use sp_api::{ProvideRuntimeApi, CallApiAt, NumberFor};
|
||||
use sp_blockchain::HeaderBackend;
|
||||
use sp_runtime::{
|
||||
Justifications, generic::{BlockId, SignedBlock}, traits::{Block as BlockT, BlakeTwo256},
|
||||
use polkadot_primitives::v1::{
|
||||
AccountId, Balance, Block, BlockNumber, Hash, Header, Nonce, ParachainHost,
|
||||
};
|
||||
use sc_client_api::{Backend as BackendT, BlockchainEvents, KeyIterator, AuxStore, UsageProvider};
|
||||
use sp_storage::{StorageData, StorageKey, ChildInfo, PrefixedStorageKey};
|
||||
use polkadot_primitives::v1::{Block, ParachainHost, AccountId, Nonce, Balance, Header, BlockNumber, Hash};
|
||||
use sp_consensus::BlockStatus;
|
||||
use sc_client_api::{AuxStore, Backend as BackendT, BlockchainEvents, KeyIterator, UsageProvider};
|
||||
use sc_executor::native_executor_instance;
|
||||
use sp_api::{CallApiAt, NumberFor, ProvideRuntimeApi};
|
||||
use sp_blockchain::HeaderBackend;
|
||||
use sp_consensus::BlockStatus;
|
||||
use sp_runtime::{
|
||||
generic::{BlockId, SignedBlock},
|
||||
traits::{BlakeTwo256, Block as BlockT},
|
||||
Justifications,
|
||||
};
|
||||
use sp_storage::{ChildInfo, PrefixedStorageKey, StorageData, StorageKey};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type FullBackend = sc_service::TFullBackend<Block>;
|
||||
|
||||
@@ -84,7 +88,8 @@ pub trait RuntimeApiCollection:
|
||||
+ beefy_primitives::BeefyApi<Block>
|
||||
where
|
||||
<Self as sp_api::ApiExt<Block>>::StateBackend: sp_api::StateBackend<BlakeTwo256>,
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
impl<Api> RuntimeApiCollection for Api
|
||||
where
|
||||
@@ -103,47 +108,47 @@ where
|
||||
+ sp_authority_discovery::AuthorityDiscoveryApi<Block>
|
||||
+ beefy_primitives::BeefyApi<Block>,
|
||||
<Self as sp_api::ApiExt<Block>>::StateBackend: sp_api::StateBackend<BlakeTwo256>,
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
/// Trait that abstracts over all available client implementations.
|
||||
///
|
||||
/// For a concrete type there exists [`Client`].
|
||||
pub trait AbstractClient<Block, Backend>:
|
||||
BlockchainEvents<Block> + Sized + Send + Sync
|
||||
BlockchainEvents<Block>
|
||||
+ Sized
|
||||
+ Send
|
||||
+ Sync
|
||||
+ ProvideRuntimeApi<Block>
|
||||
+ HeaderBackend<Block>
|
||||
+ CallApiAt<
|
||||
Block,
|
||||
StateBackend = Backend::State
|
||||
>
|
||||
+ CallApiAt<Block, StateBackend = Backend::State>
|
||||
+ AuxStore
|
||||
+ UsageProvider<Block>
|
||||
where
|
||||
Block: BlockT,
|
||||
Backend: BackendT<Block>,
|
||||
Backend::State: sp_api::StateBackend<BlakeTwo256>,
|
||||
Self::Api: RuntimeApiCollection<StateBackend = Backend::State>,
|
||||
{}
|
||||
where
|
||||
Block: BlockT,
|
||||
Backend: BackendT<Block>,
|
||||
Backend::State: sp_api::StateBackend<BlakeTwo256>,
|
||||
Self::Api: RuntimeApiCollection<StateBackend = Backend::State>,
|
||||
{
|
||||
}
|
||||
|
||||
impl<Block, Backend, Client> AbstractClient<Block, Backend> for Client
|
||||
where
|
||||
Block: BlockT,
|
||||
Backend: BackendT<Block>,
|
||||
Backend::State: sp_api::StateBackend<BlakeTwo256>,
|
||||
Client: BlockchainEvents<Block>
|
||||
+ ProvideRuntimeApi<Block>
|
||||
+ HeaderBackend<Block>
|
||||
+ AuxStore
|
||||
+ UsageProvider<Block>
|
||||
+ Sized
|
||||
+ Send
|
||||
+ Sync
|
||||
+ CallApiAt<
|
||||
Block,
|
||||
StateBackend = Backend::State
|
||||
>,
|
||||
Client::Api: RuntimeApiCollection<StateBackend = Backend::State>,
|
||||
{}
|
||||
where
|
||||
Block: BlockT,
|
||||
Backend: BackendT<Block>,
|
||||
Backend::State: sp_api::StateBackend<BlakeTwo256>,
|
||||
Client: BlockchainEvents<Block>
|
||||
+ ProvideRuntimeApi<Block>
|
||||
+ HeaderBackend<Block>
|
||||
+ AuxStore
|
||||
+ UsageProvider<Block>
|
||||
+ Sized
|
||||
+ Send
|
||||
+ Sync
|
||||
+ CallApiAt<Block, StateBackend = Backend::State>,
|
||||
Client::Api: RuntimeApiCollection<StateBackend = Backend::State>,
|
||||
{
|
||||
}
|
||||
|
||||
/// Execute something with the client instance.
|
||||
///
|
||||
@@ -162,12 +167,12 @@ pub trait ExecuteWithClient {
|
||||
|
||||
/// Execute whatever should be executed with the given client instance.
|
||||
fn execute_with_client<Client, Api, Backend>(self, client: Arc<Client>) -> Self::Output
|
||||
where
|
||||
<Api as sp_api::ApiExt<Block>>::StateBackend: sp_api::StateBackend<BlakeTwo256>,
|
||||
Backend: sc_client_api::Backend<Block> + 'static,
|
||||
Backend::State: sp_api::StateBackend<BlakeTwo256>,
|
||||
Api: crate::RuntimeApiCollection<StateBackend = Backend::State>,
|
||||
Client: AbstractClient<Block, Backend, Api = Api> + 'static;
|
||||
where
|
||||
<Api as sp_api::ApiExt<Block>>::StateBackend: sp_api::StateBackend<BlakeTwo256>,
|
||||
Backend: sc_client_api::Backend<Block> + 'static,
|
||||
Backend::State: sp_api::StateBackend<BlakeTwo256>,
|
||||
Api: crate::RuntimeApiCollection<StateBackend = Backend::State>,
|
||||
Client: AbstractClient<Block, Backend, Api = Api> + 'static;
|
||||
}
|
||||
|
||||
/// A handle to a Polkadot client instance.
|
||||
@@ -244,7 +249,7 @@ impl UsageProvider<Block> for Client {
|
||||
impl sc_client_api::BlockBackend<Block> for Client {
|
||||
fn block_body(
|
||||
&self,
|
||||
id: &BlockId<Block>
|
||||
id: &BlockId<Block>,
|
||||
) -> sp_blockchain::Result<Option<Vec<<Block as BlockT>::Extrinsic>>> {
|
||||
with_client! {
|
||||
self,
|
||||
@@ -275,10 +280,7 @@ impl sc_client_api::BlockBackend<Block> for Client {
|
||||
}
|
||||
}
|
||||
|
||||
fn justifications(
|
||||
&self,
|
||||
id: &BlockId<Block>
|
||||
) -> sp_blockchain::Result<Option<Justifications>> {
|
||||
fn justifications(&self, id: &BlockId<Block>) -> sp_blockchain::Result<Option<Justifications>> {
|
||||
with_client! {
|
||||
self,
|
||||
client,
|
||||
@@ -290,7 +292,7 @@ impl sc_client_api::BlockBackend<Block> for Client {
|
||||
|
||||
fn block_hash(
|
||||
&self,
|
||||
number: NumberFor<Block>
|
||||
number: NumberFor<Block>,
|
||||
) -> sp_blockchain::Result<Option<<Block as BlockT>::Hash>> {
|
||||
with_client! {
|
||||
self,
|
||||
@@ -303,7 +305,7 @@ impl sc_client_api::BlockBackend<Block> for Client {
|
||||
|
||||
fn indexed_transaction(
|
||||
&self,
|
||||
id: &<Block as BlockT>::Hash
|
||||
id: &<Block as BlockT>::Hash,
|
||||
) -> sp_blockchain::Result<Option<Vec<u8>>> {
|
||||
with_client! {
|
||||
self,
|
||||
@@ -316,7 +318,7 @@ impl sc_client_api::BlockBackend<Block> for Client {
|
||||
|
||||
fn block_indexed_body(
|
||||
&self,
|
||||
id: &BlockId<Block>
|
||||
id: &BlockId<Block>,
|
||||
) -> sp_blockchain::Result<Option<Vec<Vec<u8>>>> {
|
||||
with_client! {
|
||||
self,
|
||||
@@ -390,7 +392,9 @@ impl sc_client_api::StorageProvider<Block, crate::FullBackend> for Client {
|
||||
id: &BlockId<Block>,
|
||||
prefix: Option<&'a StorageKey>,
|
||||
start_key: Option<&StorageKey>,
|
||||
) -> sp_blockchain::Result<KeyIterator<'a, <crate::FullBackend as sc_client_api::Backend<Block>>::State, Block>> {
|
||||
) -> sp_blockchain::Result<
|
||||
KeyIterator<'a, <crate::FullBackend as sc_client_api::Backend<Block>>::State, Block>,
|
||||
> {
|
||||
with_client! {
|
||||
self,
|
||||
client,
|
||||
@@ -436,7 +440,9 @@ impl sc_client_api::StorageProvider<Block, crate::FullBackend> for Client {
|
||||
child_info: ChildInfo,
|
||||
prefix: Option<&'a StorageKey>,
|
||||
start_key: Option<&StorageKey>,
|
||||
) -> sp_blockchain::Result<KeyIterator<'a, <crate::FullBackend as sc_client_api::Backend<Block>>::State, Block>> {
|
||||
) -> sp_blockchain::Result<
|
||||
KeyIterator<'a, <crate::FullBackend as sc_client_api::Backend<Block>>::State, Block>,
|
||||
> {
|
||||
with_client! {
|
||||
self,
|
||||
client,
|
||||
|
||||
@@ -18,35 +18,23 @@
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use futures::{
|
||||
channel::mpsc,
|
||||
future::FutureExt,
|
||||
join,
|
||||
select,
|
||||
sink::SinkExt,
|
||||
stream::StreamExt,
|
||||
};
|
||||
use polkadot_node_primitives::{
|
||||
CollationGenerationConfig, AvailableData, PoV,
|
||||
};
|
||||
use futures::{channel::mpsc, future::FutureExt, join, select, sink::SinkExt, stream::StreamExt};
|
||||
use parity_scale_codec::Encode;
|
||||
use polkadot_node_primitives::{AvailableData, CollationGenerationConfig, PoV};
|
||||
use polkadot_node_subsystem::{
|
||||
ActiveLeavesUpdate,
|
||||
messages::{AllMessages, CollationGenerationMessage, CollatorProtocolMessage},
|
||||
SpawnedSubsystem, SubsystemContext, SubsystemResult,
|
||||
SubsystemError, FromOverseer, OverseerSignal,
|
||||
overseer,
|
||||
overseer, ActiveLeavesUpdate, FromOverseer, OverseerSignal, SpawnedSubsystem, SubsystemContext,
|
||||
SubsystemError, SubsystemResult,
|
||||
};
|
||||
use polkadot_node_subsystem_util::{
|
||||
request_availability_cores, request_persisted_validation_data,
|
||||
request_validators, request_validation_code,
|
||||
metrics::{self, prometheus},
|
||||
request_availability_cores, request_persisted_validation_data, request_validation_code,
|
||||
request_validators,
|
||||
};
|
||||
use polkadot_primitives::v1::{
|
||||
collator_signature_payload, CandidateCommitments,
|
||||
CandidateDescriptor, CandidateReceipt, CoreState, Hash, OccupiedCoreAssumption,
|
||||
PersistedValidationData,
|
||||
collator_signature_payload, CandidateCommitments, CandidateDescriptor, CandidateReceipt,
|
||||
CoreState, Hash, OccupiedCoreAssumption, PersistedValidationData,
|
||||
};
|
||||
use parity_scale_codec::Encode;
|
||||
use sp_core::crypto::Pair;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -66,10 +54,7 @@ pub struct CollationGenerationSubsystem {
|
||||
impl CollationGenerationSubsystem {
|
||||
/// Create a new instance of the `CollationGenerationSubsystem`.
|
||||
pub fn new(metrics: Metrics) -> Self {
|
||||
Self {
|
||||
config: None,
|
||||
metrics,
|
||||
}
|
||||
Self { config: None, metrics }
|
||||
}
|
||||
|
||||
/// Run this subsystem
|
||||
@@ -127,7 +112,10 @@ impl CollationGenerationSubsystem {
|
||||
Context: overseer::SubsystemContext<Message = CollationGenerationMessage>,
|
||||
{
|
||||
match incoming {
|
||||
Ok(FromOverseer::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { activated, .. }))) => {
|
||||
Ok(FromOverseer::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate {
|
||||
activated,
|
||||
..
|
||||
}))) => {
|
||||
// follow the procedure from the guide
|
||||
if let Some(config) = &self.config {
|
||||
let metrics = self.metrics.clone();
|
||||
@@ -137,13 +125,15 @@ impl CollationGenerationSubsystem {
|
||||
ctx,
|
||||
metrics,
|
||||
sender,
|
||||
).await {
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::warn!(target: LOG_TARGET, err = ?err, "failed to handle new activations");
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
},
|
||||
Ok(FromOverseer::Signal(OverseerSignal::Conclude)) => true,
|
||||
Ok(FromOverseer::Communication {
|
||||
msg: CollationGenerationMessage::Initialize(config),
|
||||
@@ -154,7 +144,7 @@ impl CollationGenerationSubsystem {
|
||||
self.config = Some(Arc::new(config));
|
||||
}
|
||||
false
|
||||
}
|
||||
},
|
||||
Ok(FromOverseer::Signal(OverseerSignal::BlockFinalized(..))) => false,
|
||||
Err(err) => {
|
||||
tracing::error!(
|
||||
@@ -164,7 +154,7 @@ impl CollationGenerationSubsystem {
|
||||
err
|
||||
);
|
||||
true
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,12 +168,10 @@ where
|
||||
let future = async move {
|
||||
self.run(ctx).await;
|
||||
Ok(())
|
||||
}.boxed();
|
||||
|
||||
SpawnedSubsystem {
|
||||
name: "collation-generation-subsystem",
|
||||
future,
|
||||
}
|
||||
.boxed();
|
||||
|
||||
SpawnedSubsystem { name: "collation-generation-subsystem", future }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,9 +202,8 @@ async fn handle_new_activations<Context: SubsystemContext>(
|
||||
let _availability_core_timer = metrics.time_new_activations_availability_core();
|
||||
|
||||
let (scheduled_core, assumption) = match core {
|
||||
CoreState::Scheduled(scheduled_core) => {
|
||||
(scheduled_core, OccupiedCoreAssumption::Free)
|
||||
}
|
||||
CoreState::Scheduled(scheduled_core) =>
|
||||
(scheduled_core, OccupiedCoreAssumption::Free),
|
||||
CoreState::Occupied(_occupied_core) => {
|
||||
// TODO: https://github.com/paritytech/polkadot/issues/1573
|
||||
tracing::trace!(
|
||||
@@ -225,8 +212,8 @@ async fn handle_new_activations<Context: SubsystemContext>(
|
||||
relay_parent = ?relay_parent,
|
||||
"core is occupied. Keep going.",
|
||||
);
|
||||
continue;
|
||||
}
|
||||
continue
|
||||
},
|
||||
CoreState::Free => {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
@@ -234,7 +221,7 @@ async fn handle_new_activations<Context: SubsystemContext>(
|
||||
"core is free. Keep going.",
|
||||
);
|
||||
continue
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if scheduled_core.para_id != config.para_id {
|
||||
@@ -246,7 +233,7 @@ async fn handle_new_activations<Context: SubsystemContext>(
|
||||
their_para = %scheduled_core.para_id,
|
||||
"core is not assigned to our para. Keep going.",
|
||||
);
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
|
||||
// we get validation data and validation code synchronously for each core instead of
|
||||
@@ -273,7 +260,7 @@ async fn handle_new_activations<Context: SubsystemContext>(
|
||||
"validation data is not available",
|
||||
);
|
||||
continue
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let validation_code = match request_validation_code(
|
||||
@@ -296,125 +283,131 @@ async fn handle_new_activations<Context: SubsystemContext>(
|
||||
"validation code is not available",
|
||||
);
|
||||
continue
|
||||
}
|
||||
},
|
||||
};
|
||||
let validation_code_hash = validation_code.hash();
|
||||
|
||||
let task_config = config.clone();
|
||||
let mut task_sender = sender.clone();
|
||||
let metrics = metrics.clone();
|
||||
ctx.spawn("collation generation collation builder", Box::pin(async move {
|
||||
let persisted_validation_data_hash = validation_data.hash();
|
||||
ctx.spawn(
|
||||
"collation generation collation builder",
|
||||
Box::pin(async move {
|
||||
let persisted_validation_data_hash = validation_data.hash();
|
||||
|
||||
let (collation, result_sender) = match (task_config.collator)(relay_parent, &validation_data).await {
|
||||
Some(collation) => collation.into_inner(),
|
||||
None => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
para_id = %scheduled_core.para_id,
|
||||
"collator returned no collation on collate",
|
||||
let (collation, result_sender) =
|
||||
match (task_config.collator)(relay_parent, &validation_data).await {
|
||||
Some(collation) => collation.into_inner(),
|
||||
None => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
para_id = %scheduled_core.para_id,
|
||||
"collator returned no collation on collate",
|
||||
);
|
||||
return
|
||||
},
|
||||
};
|
||||
|
||||
// Apply compression to the block data.
|
||||
let pov = {
|
||||
let pov = polkadot_node_primitives::maybe_compress_pov(
|
||||
collation.proof_of_validity,
|
||||
);
|
||||
return
|
||||
}
|
||||
};
|
||||
let encoded_size = pov.encoded_size();
|
||||
|
||||
// Apply compression to the block data.
|
||||
let pov = {
|
||||
let pov = polkadot_node_primitives::maybe_compress_pov(collation.proof_of_validity);
|
||||
let encoded_size = pov.encoded_size();
|
||||
// As long as `POV_BOMB_LIMIT` is at least `max_pov_size`, this ensures
|
||||
// that honest collators never produce a PoV which is uncompressed.
|
||||
//
|
||||
// As such, honest collators never produce an uncompressed PoV which starts with
|
||||
// a compression magic number, which would lead validators to reject the collation.
|
||||
if encoded_size > validation_data.max_pov_size as usize {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
para_id = %scheduled_core.para_id,
|
||||
size = encoded_size,
|
||||
max_size = validation_data.max_pov_size,
|
||||
"PoV exceeded maximum size"
|
||||
);
|
||||
|
||||
// As long as `POV_BOMB_LIMIT` is at least `max_pov_size`, this ensures
|
||||
// that honest collators never produce a PoV which is uncompressed.
|
||||
//
|
||||
// As such, honest collators never produce an uncompressed PoV which starts with
|
||||
// a compression magic number, which would lead validators to reject the collation.
|
||||
if encoded_size > validation_data.max_pov_size as usize {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
para_id = %scheduled_core.para_id,
|
||||
size = encoded_size,
|
||||
max_size = validation_data.max_pov_size,
|
||||
"PoV exceeded maximum size"
|
||||
);
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
pov
|
||||
};
|
||||
|
||||
pov
|
||||
};
|
||||
let pov_hash = pov.hash();
|
||||
|
||||
let pov_hash = pov.hash();
|
||||
let signature_payload = collator_signature_payload(
|
||||
&relay_parent,
|
||||
&scheduled_core.para_id,
|
||||
&persisted_validation_data_hash,
|
||||
&pov_hash,
|
||||
&validation_code_hash,
|
||||
);
|
||||
|
||||
let signature_payload = collator_signature_payload(
|
||||
&relay_parent,
|
||||
&scheduled_core.para_id,
|
||||
&persisted_validation_data_hash,
|
||||
&pov_hash,
|
||||
&validation_code_hash,
|
||||
);
|
||||
let erasure_root =
|
||||
match erasure_root(n_validators, validation_data, pov.clone()) {
|
||||
Ok(erasure_root) => erasure_root,
|
||||
Err(err) => {
|
||||
tracing::error!(
|
||||
target: LOG_TARGET,
|
||||
para_id = %scheduled_core.para_id,
|
||||
err = ?err,
|
||||
"failed to calculate erasure root",
|
||||
);
|
||||
return
|
||||
},
|
||||
};
|
||||
|
||||
let erasure_root = match erasure_root(
|
||||
n_validators,
|
||||
validation_data,
|
||||
pov.clone(),
|
||||
) {
|
||||
Ok(erasure_root) => erasure_root,
|
||||
Err(err) => {
|
||||
tracing::error!(
|
||||
let commitments = CandidateCommitments {
|
||||
upward_messages: collation.upward_messages,
|
||||
horizontal_messages: collation.horizontal_messages,
|
||||
new_validation_code: collation.new_validation_code,
|
||||
head_data: collation.head_data,
|
||||
processed_downward_messages: collation.processed_downward_messages,
|
||||
hrmp_watermark: collation.hrmp_watermark,
|
||||
};
|
||||
|
||||
let ccr = CandidateReceipt {
|
||||
commitments_hash: commitments.hash(),
|
||||
descriptor: CandidateDescriptor {
|
||||
signature: task_config.key.sign(&signature_payload),
|
||||
para_id: scheduled_core.para_id,
|
||||
relay_parent,
|
||||
collator: task_config.key.public(),
|
||||
persisted_validation_data_hash,
|
||||
pov_hash,
|
||||
erasure_root,
|
||||
para_head: commitments.head_data.hash(),
|
||||
validation_code_hash,
|
||||
},
|
||||
};
|
||||
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
candidate_hash = ?ccr.hash(),
|
||||
?pov_hash,
|
||||
?relay_parent,
|
||||
para_id = %scheduled_core.para_id,
|
||||
"candidate is generated",
|
||||
);
|
||||
metrics.on_collation_generated();
|
||||
|
||||
if let Err(err) = task_sender
|
||||
.send(AllMessages::CollatorProtocol(
|
||||
CollatorProtocolMessage::DistributeCollation(ccr, pov, result_sender),
|
||||
))
|
||||
.await
|
||||
{
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
para_id = %scheduled_core.para_id,
|
||||
err = ?err,
|
||||
"failed to calculate erasure root",
|
||||
"failed to send collation result",
|
||||
);
|
||||
return
|
||||
}
|
||||
};
|
||||
|
||||
let commitments = CandidateCommitments {
|
||||
upward_messages: collation.upward_messages,
|
||||
horizontal_messages: collation.horizontal_messages,
|
||||
new_validation_code: collation.new_validation_code,
|
||||
head_data: collation.head_data,
|
||||
processed_downward_messages: collation.processed_downward_messages,
|
||||
hrmp_watermark: collation.hrmp_watermark,
|
||||
};
|
||||
|
||||
let ccr = CandidateReceipt {
|
||||
commitments_hash: commitments.hash(),
|
||||
descriptor: CandidateDescriptor {
|
||||
signature: task_config.key.sign(&signature_payload),
|
||||
para_id: scheduled_core.para_id,
|
||||
relay_parent,
|
||||
collator: task_config.key.public(),
|
||||
persisted_validation_data_hash,
|
||||
pov_hash,
|
||||
erasure_root,
|
||||
para_head: commitments.head_data.hash(),
|
||||
validation_code_hash: validation_code_hash,
|
||||
},
|
||||
};
|
||||
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
candidate_hash = ?ccr.hash(),
|
||||
?pov_hash,
|
||||
?relay_parent,
|
||||
para_id = %scheduled_core.para_id,
|
||||
"candidate is generated",
|
||||
);
|
||||
metrics.on_collation_generated();
|
||||
|
||||
if let Err(err) = task_sender.send(AllMessages::CollatorProtocol(
|
||||
CollatorProtocolMessage::DistributeCollation(ccr, pov, result_sender)
|
||||
)).await {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
para_id = %scheduled_core.para_id,
|
||||
err = ?err,
|
||||
"failed to send collation result",
|
||||
);
|
||||
}
|
||||
}))?;
|
||||
}),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,10 +419,8 @@ fn erasure_root(
|
||||
persisted_validation: PersistedValidationData,
|
||||
pov: PoV,
|
||||
) -> crate::error::Result<Hash> {
|
||||
let available_data = AvailableData {
|
||||
validation_data: persisted_validation,
|
||||
pov: Arc::new(pov),
|
||||
};
|
||||
let available_data =
|
||||
AvailableData { validation_data: persisted_validation, pov: Arc::new(pov) };
|
||||
|
||||
let chunks = polkadot_erasure_coding::obtain_chunks_v1(n_validators, &available_data)?;
|
||||
Ok(polkadot_erasure_coding::branches(&chunks).root())
|
||||
@@ -460,13 +451,21 @@ impl Metrics {
|
||||
}
|
||||
|
||||
/// Provide a timer per relay parents which updates on drop.
|
||||
fn time_new_activations_relay_parent(&self) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
|
||||
self.0.as_ref().map(|metrics| metrics.new_activations_per_relay_parent.start_timer())
|
||||
fn time_new_activations_relay_parent(
|
||||
&self,
|
||||
) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
|
||||
self.0
|
||||
.as_ref()
|
||||
.map(|metrics| metrics.new_activations_per_relay_parent.start_timer())
|
||||
}
|
||||
|
||||
/// Provide a timer per availability core which updates on drop.
|
||||
fn time_new_activations_availability_core(&self) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
|
||||
self.0.as_ref().map(|metrics| metrics.new_activations_per_availability_core.start_timer())
|
||||
fn time_new_activations_availability_core(
|
||||
&self,
|
||||
) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
|
||||
self.0
|
||||
.as_ref()
|
||||
.map(|metrics| metrics.new_activations_per_availability_core.start_timer())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,10 +21,8 @@ mod handle_new_activations {
|
||||
task::{Context as FuturesContext, Poll},
|
||||
Future,
|
||||
};
|
||||
use polkadot_node_primitives::{Collation, CollationResult, BlockData, PoV, POV_BOMB_LIMIT};
|
||||
use polkadot_node_subsystem::messages::{
|
||||
AllMessages, RuntimeApiMessage, RuntimeApiRequest,
|
||||
};
|
||||
use polkadot_node_primitives::{BlockData, Collation, CollationResult, PoV, POV_BOMB_LIMIT};
|
||||
use polkadot_node_subsystem::messages::{AllMessages, RuntimeApiMessage, RuntimeApiRequest};
|
||||
use polkadot_node_subsystem_test_helpers::{
|
||||
subsystem_test_harness, TestSubsystemContextHandle,
|
||||
};
|
||||
@@ -39,9 +37,7 @@ mod handle_new_activations {
|
||||
horizontal_messages: Default::default(),
|
||||
new_validation_code: Default::default(),
|
||||
head_data: Default::default(),
|
||||
proof_of_validity: PoV {
|
||||
block_data: BlockData(Vec::new()),
|
||||
},
|
||||
proof_of_validity: PoV { block_data: BlockData(Vec::new()) },
|
||||
processed_downward_messages: Default::default(),
|
||||
hrmp_watermark: Default::default(),
|
||||
}
|
||||
@@ -50,10 +46,13 @@ mod handle_new_activations {
|
||||
fn test_collation_compressed() -> Collation {
|
||||
let mut collation = test_collation();
|
||||
let compressed = PoV {
|
||||
block_data: BlockData(sp_maybe_compressed_blob::compress(
|
||||
&collation.proof_of_validity.block_data.0,
|
||||
POV_BOMB_LIMIT,
|
||||
).unwrap())
|
||||
block_data: BlockData(
|
||||
sp_maybe_compressed_blob::compress(
|
||||
&collation.proof_of_validity.block_data.0,
|
||||
POV_BOMB_LIMIT,
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
};
|
||||
collation.proof_of_validity = compressed;
|
||||
collation
|
||||
@@ -81,28 +80,19 @@ mod handle_new_activations {
|
||||
fn test_config<Id: Into<ParaId>>(para_id: Id) -> Arc<CollationGenerationConfig> {
|
||||
Arc::new(CollationGenerationConfig {
|
||||
key: CollatorPair::generate().0,
|
||||
collator: Box::new(|_: Hash, _vd: &PersistedValidationData| {
|
||||
TestCollator.boxed()
|
||||
}),
|
||||
collator: Box::new(|_: Hash, _vd: &PersistedValidationData| TestCollator.boxed()),
|
||||
para_id: para_id.into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn scheduled_core_for<Id: Into<ParaId>>(para_id: Id) -> ScheduledCore {
|
||||
ScheduledCore {
|
||||
para_id: para_id.into(),
|
||||
collator: None,
|
||||
}
|
||||
ScheduledCore { para_id: para_id.into(), collator: None }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn requests_availability_per_relay_parent() {
|
||||
let activated_hashes: Vec<Hash> = vec![
|
||||
[1; 32].into(),
|
||||
[4; 32].into(),
|
||||
[9; 32].into(),
|
||||
[16; 32].into(),
|
||||
];
|
||||
let activated_hashes: Vec<Hash> =
|
||||
vec![[1; 32].into(), [4; 32].into(), [9; 32].into(), [16; 32].into()];
|
||||
|
||||
let requested_availability_cores = Arc::new(Mutex::new(Vec::new()));
|
||||
|
||||
@@ -177,7 +167,7 @@ mod handle_new_activations {
|
||||
)),
|
||||
]))
|
||||
.unwrap();
|
||||
}
|
||||
},
|
||||
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
hash,
|
||||
RuntimeApiRequest::PersistedValidationData(
|
||||
@@ -186,21 +176,18 @@ mod handle_new_activations {
|
||||
tx,
|
||||
),
|
||||
))) => {
|
||||
overseer_requested_validation_data
|
||||
.lock()
|
||||
.await
|
||||
.push(hash);
|
||||
overseer_requested_validation_data.lock().await.push(hash);
|
||||
tx.send(Ok(Default::default())).unwrap();
|
||||
}
|
||||
},
|
||||
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
_hash,
|
||||
RuntimeApiRequest::Validators(tx),
|
||||
))) => {
|
||||
tx.send(Ok(vec![Default::default(); 3])).unwrap();
|
||||
}
|
||||
},
|
||||
Some(msg) => {
|
||||
panic!("didn't expect any other overseer requests; got {:?}", msg)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -252,7 +239,7 @@ mod handle_new_activations {
|
||||
)),
|
||||
]))
|
||||
.unwrap();
|
||||
}
|
||||
},
|
||||
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
_hash,
|
||||
RuntimeApiRequest::PersistedValidationData(
|
||||
@@ -262,13 +249,13 @@ mod handle_new_activations {
|
||||
),
|
||||
))) => {
|
||||
tx.send(Ok(Some(test_validation_data()))).unwrap();
|
||||
}
|
||||
},
|
||||
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
_hash,
|
||||
RuntimeApiRequest::Validators(tx),
|
||||
))) => {
|
||||
tx.send(Ok(vec![Default::default(); 3])).unwrap();
|
||||
}
|
||||
},
|
||||
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
_hash,
|
||||
RuntimeApiRequest::ValidationCode(
|
||||
@@ -278,10 +265,10 @@ mod handle_new_activations {
|
||||
),
|
||||
))) => {
|
||||
tx.send(Ok(Some(ValidationCode(vec![1, 2, 3])))).unwrap();
|
||||
}
|
||||
},
|
||||
Some(msg) => {
|
||||
panic!("didn't expect any other overseer requests; got {:?}", msg)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -295,9 +282,15 @@ mod handle_new_activations {
|
||||
let sent_messages = Arc::new(Mutex::new(Vec::new()));
|
||||
let subsystem_sent_messages = sent_messages.clone();
|
||||
subsystem_test_harness(overseer, |mut ctx| async move {
|
||||
handle_new_activations(subsystem_config, activated_hashes, &mut ctx, Metrics(None), &tx)
|
||||
.await
|
||||
.unwrap();
|
||||
handle_new_activations(
|
||||
subsystem_config,
|
||||
activated_hashes,
|
||||
&mut ctx,
|
||||
Metrics(None),
|
||||
&tx,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
std::mem::drop(tx);
|
||||
|
||||
@@ -340,7 +333,7 @@ mod handle_new_activations {
|
||||
AllMessages::CollatorProtocol(CollatorProtocolMessage::DistributeCollation(
|
||||
CandidateReceipt { descriptor, .. },
|
||||
_pov,
|
||||
..
|
||||
..,
|
||||
)) => {
|
||||
// signature generation is non-deterministic, so we can't just assert that the
|
||||
// expected descriptor is correct. What we can do is validate that the produced
|
||||
@@ -365,7 +358,7 @@ mod handle_new_activations {
|
||||
expect_descriptor
|
||||
};
|
||||
assert_eq!(descriptor, &expect_descriptor);
|
||||
}
|
||||
},
|
||||
_ => panic!("received wrong message type"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
|
||||
//! Utilities for checking whether a candidate has been approved under a given block.
|
||||
|
||||
use bitvec::{order::Lsb0 as BitOrderLsb0, slice::BitSlice};
|
||||
use polkadot_node_primitives::approval::DelayTranche;
|
||||
use polkadot_primitives::v1::ValidatorIndex;
|
||||
use bitvec::slice::BitSlice;
|
||||
use bitvec::order::Lsb0 as BitOrderLsb0;
|
||||
|
||||
use crate::persisted_entries::{TrancheEntry, ApprovalEntry, CandidateEntry};
|
||||
use crate::time::Tick;
|
||||
use crate::{
|
||||
persisted_entries::{ApprovalEntry, CandidateEntry, TrancheEntry},
|
||||
time::Tick,
|
||||
};
|
||||
|
||||
/// The required tranches of assignments needed to determine whether a candidate is approved.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
@@ -57,7 +58,7 @@ pub enum RequiredTranches {
|
||||
/// event that there are some assignments that don't have corresponding approval votes. If this
|
||||
/// is `None`, all assignments have approvals.
|
||||
next_no_show: Option<Tick>,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/// The result of a check.
|
||||
@@ -96,12 +97,11 @@ pub fn check_approval(
|
||||
approval: &ApprovalEntry,
|
||||
required: RequiredTranches,
|
||||
) -> Check {
|
||||
|
||||
// any set of size f+1 contains at least one honest node. If at least one
|
||||
// honest node approves, the candidate should be approved.
|
||||
let approvals = candidate.approvals();
|
||||
if 3 * approvals.count_ones() > approvals.len() {
|
||||
return Check::ApprovedOneThird;
|
||||
return Check::ApprovedOneThird
|
||||
}
|
||||
|
||||
match required {
|
||||
@@ -134,7 +134,7 @@ pub fn check_approval(
|
||||
} else {
|
||||
Check::Unapproved
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ impl State {
|
||||
) -> RequiredTranches {
|
||||
let covering = if self.depth == 0 { 0 } else { self.covering };
|
||||
if self.depth != 0 && self.assignments + covering + self.uncovered >= n_validators {
|
||||
return RequiredTranches::All;
|
||||
return RequiredTranches::All
|
||||
}
|
||||
|
||||
// If we have enough assignments and all no-shows are covered, we have reached the number
|
||||
@@ -192,7 +192,7 @@ impl State {
|
||||
needed: tranche,
|
||||
tolerated_missing: self.covered,
|
||||
next_no_show: self.next_no_show,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// We're pending more assignments and should look at more tranches.
|
||||
@@ -245,10 +245,7 @@ impl State {
|
||||
self.covered + new_covered
|
||||
};
|
||||
let uncovered = self.uncovered + new_no_shows;
|
||||
let next_no_show = super::min_prefer_some(
|
||||
self.next_no_show,
|
||||
next_no_show,
|
||||
);
|
||||
let next_no_show = super::min_prefer_some(self.next_no_show, next_no_show);
|
||||
|
||||
let (depth, covering, uncovered) = if covering == 0 {
|
||||
if uncovered == 0 {
|
||||
@@ -271,27 +268,25 @@ fn filled_tranche_iterator<'a>(
|
||||
) -> impl Iterator<Item = (DelayTranche, &[(ValidatorIndex, Tick)])> {
|
||||
let mut gap_end = None;
|
||||
|
||||
let approval_entries_filled = tranches
|
||||
.iter()
|
||||
.flat_map(move |tranche_entry| {
|
||||
let tranche = tranche_entry.tranche();
|
||||
let assignments = tranche_entry.assignments();
|
||||
let approval_entries_filled = tranches.iter().flat_map(move |tranche_entry| {
|
||||
let tranche = tranche_entry.tranche();
|
||||
let assignments = tranche_entry.assignments();
|
||||
|
||||
// The new gap_start immediately follows the prior gap_end, if one exists.
|
||||
// Otherwise, on the first pass, the new gap_start is set to the first
|
||||
// tranche so that the range below will be empty.
|
||||
let gap_start = gap_end.map(|end| end + 1).unwrap_or(tranche);
|
||||
gap_end = Some(tranche);
|
||||
// The new gap_start immediately follows the prior gap_end, if one exists.
|
||||
// Otherwise, on the first pass, the new gap_start is set to the first
|
||||
// tranche so that the range below will be empty.
|
||||
let gap_start = gap_end.map(|end| end + 1).unwrap_or(tranche);
|
||||
gap_end = Some(tranche);
|
||||
|
||||
(gap_start..tranche).map(|i| (i, &[] as &[_]))
|
||||
.chain(std::iter::once((tranche, assignments)))
|
||||
});
|
||||
(gap_start..tranche)
|
||||
.map(|i| (i, &[] as &[_]))
|
||||
.chain(std::iter::once((tranche, assignments)))
|
||||
});
|
||||
|
||||
let pre_end = tranches.first().map(|t| t.tranche());
|
||||
let post_start = tranches.last().map_or(0, |t| t.tranche() + 1);
|
||||
|
||||
let pre = pre_end.into_iter()
|
||||
.flat_map(|pre_end| (0..pre_end).map(|i| (i, &[] as &[_])));
|
||||
let pre = pre_end.into_iter().flat_map(|pre_end| (0..pre_end).map(|i| (i, &[] as &[_])));
|
||||
let post = (post_start..).map(|i| (i, &[] as &[_]));
|
||||
|
||||
pre.chain(approval_entries_filled).chain(post)
|
||||
@@ -313,13 +308,14 @@ fn count_no_shows(
|
||||
drifted_tick_now: Tick,
|
||||
) -> (usize, Option<u64>) {
|
||||
let mut next_no_show = None;
|
||||
let no_shows = assignments.iter()
|
||||
let no_shows = assignments
|
||||
.iter()
|
||||
.map(|(v_index, tick)| (v_index, tick.saturating_sub(clock_drift) + no_show_duration))
|
||||
.filter(|&(v_index, no_show_at)| {
|
||||
let has_approved = if let Some(approved) = approvals.get(v_index.0 as usize) {
|
||||
*approved
|
||||
} else {
|
||||
return false;
|
||||
return false
|
||||
};
|
||||
|
||||
let is_no_show = !has_approved && no_show_at <= drifted_tick_now;
|
||||
@@ -331,14 +327,12 @@ fn count_no_shows(
|
||||
// *this node* should observe whether or not the validator is a no show. Recall
|
||||
// that when the when drifted_tick_now is computed during that subsequent wake up,
|
||||
// the clock drift will be removed again to do the comparison above.
|
||||
next_no_show = super::min_prefer_some(
|
||||
next_no_show,
|
||||
Some(no_show_at + clock_drift),
|
||||
);
|
||||
next_no_show = super::min_prefer_some(next_no_show, Some(no_show_at + clock_drift));
|
||||
}
|
||||
|
||||
is_no_show
|
||||
}).count();
|
||||
})
|
||||
.count();
|
||||
|
||||
(no_shows, next_no_show)
|
||||
}
|
||||
@@ -430,9 +424,8 @@ pub fn tranches_to_approve(
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use bitvec::{bitvec, order::Lsb0 as BitOrderLsb0};
|
||||
use polkadot_primitives::v1::GroupIndex;
|
||||
use bitvec::bitvec;
|
||||
use bitvec::order::Lsb0 as BitOrderLsb0;
|
||||
|
||||
use crate::approval_db;
|
||||
|
||||
@@ -443,7 +436,8 @@ mod tests {
|
||||
session: 0,
|
||||
block_assignments: Default::default(),
|
||||
approvals: Default::default(),
|
||||
}.into();
|
||||
}
|
||||
.into();
|
||||
|
||||
let approval_entry = approval_db::v1::ApprovalEntry {
|
||||
tranches: Vec::new(),
|
||||
@@ -452,7 +446,8 @@ mod tests {
|
||||
our_approval_sig: None,
|
||||
backing_group: GroupIndex(0),
|
||||
approved: false,
|
||||
}.into();
|
||||
}
|
||||
.into();
|
||||
|
||||
assert!(!check_approval(
|
||||
&candidate,
|
||||
@@ -463,7 +458,8 @@ mod tests {
|
||||
maximum_broadcast: 0,
|
||||
clock_drift: 0,
|
||||
},
|
||||
).is_approved());
|
||||
)
|
||||
.is_approved());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -473,7 +469,8 @@ mod tests {
|
||||
session: 0,
|
||||
block_assignments: Default::default(),
|
||||
approvals: bitvec![BitOrderLsb0, u8; 0; 10],
|
||||
}.into();
|
||||
}
|
||||
.into();
|
||||
|
||||
for i in 0..3 {
|
||||
candidate.mark_approval(ValidatorIndex(i));
|
||||
@@ -499,35 +496,27 @@ mod tests {
|
||||
our_approval_sig: None,
|
||||
backing_group: GroupIndex(0),
|
||||
approved: false,
|
||||
}.into();
|
||||
}
|
||||
.into();
|
||||
|
||||
assert!(check_approval(
|
||||
&candidate,
|
||||
&approval_entry,
|
||||
RequiredTranches::Exact {
|
||||
needed: 0,
|
||||
tolerated_missing: 0,
|
||||
next_no_show: None,
|
||||
},
|
||||
).is_approved());
|
||||
RequiredTranches::Exact { needed: 0, tolerated_missing: 0, next_no_show: None },
|
||||
)
|
||||
.is_approved());
|
||||
assert!(!check_approval(
|
||||
&candidate,
|
||||
&approval_entry,
|
||||
RequiredTranches::Exact {
|
||||
needed: 1,
|
||||
tolerated_missing: 0,
|
||||
next_no_show: None,
|
||||
},
|
||||
).is_approved());
|
||||
RequiredTranches::Exact { needed: 1, tolerated_missing: 0, next_no_show: None },
|
||||
)
|
||||
.is_approved());
|
||||
assert!(check_approval(
|
||||
&candidate,
|
||||
&approval_entry,
|
||||
RequiredTranches::Exact {
|
||||
needed: 1,
|
||||
tolerated_missing: 2,
|
||||
next_no_show: None,
|
||||
},
|
||||
).is_approved());
|
||||
RequiredTranches::Exact { needed: 1, tolerated_missing: 2, next_no_show: None },
|
||||
)
|
||||
.is_approved());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -537,7 +526,8 @@ mod tests {
|
||||
session: 0,
|
||||
block_assignments: Default::default(),
|
||||
approvals: bitvec![BitOrderLsb0, u8; 0; 10],
|
||||
}.into();
|
||||
}
|
||||
.into();
|
||||
|
||||
for i in 0..3 {
|
||||
candidate.mark_approval(ValidatorIndex(i));
|
||||
@@ -563,13 +553,11 @@ mod tests {
|
||||
our_approval_sig: None,
|
||||
backing_group: GroupIndex(0),
|
||||
approved: false,
|
||||
}.into();
|
||||
}
|
||||
.into();
|
||||
|
||||
let exact_all = RequiredTranches::Exact {
|
||||
needed: 10,
|
||||
tolerated_missing: 0,
|
||||
next_no_show: None,
|
||||
};
|
||||
let exact_all =
|
||||
RequiredTranches::Exact { needed: 10, tolerated_missing: 0, next_no_show: None };
|
||||
|
||||
let pending_all = RequiredTranches::Pending {
|
||||
considered: 5,
|
||||
@@ -578,44 +566,20 @@ mod tests {
|
||||
clock_drift: 12,
|
||||
};
|
||||
|
||||
assert!(!check_approval(
|
||||
&candidate,
|
||||
&approval_entry,
|
||||
RequiredTranches::All,
|
||||
).is_approved());
|
||||
assert!(!check_approval(&candidate, &approval_entry, RequiredTranches::All,).is_approved());
|
||||
|
||||
assert!(!check_approval(
|
||||
&candidate,
|
||||
&approval_entry,
|
||||
exact_all.clone(),
|
||||
).is_approved());
|
||||
assert!(!check_approval(&candidate, &approval_entry, exact_all.clone(),).is_approved());
|
||||
|
||||
assert!(!check_approval(
|
||||
&candidate,
|
||||
&approval_entry,
|
||||
pending_all.clone(),
|
||||
).is_approved());
|
||||
assert!(!check_approval(&candidate, &approval_entry, pending_all.clone(),).is_approved());
|
||||
|
||||
// This creates a set of 4/10 approvals, which is always an approval.
|
||||
candidate.mark_approval(ValidatorIndex(3));
|
||||
|
||||
assert!(check_approval(
|
||||
&candidate,
|
||||
&approval_entry,
|
||||
RequiredTranches::All,
|
||||
).is_approved());
|
||||
assert!(check_approval(&candidate, &approval_entry, RequiredTranches::All,).is_approved());
|
||||
|
||||
assert!(check_approval(
|
||||
&candidate,
|
||||
&approval_entry,
|
||||
exact_all,
|
||||
).is_approved());
|
||||
assert!(check_approval(&candidate, &approval_entry, exact_all,).is_approved());
|
||||
|
||||
assert!(check_approval(
|
||||
&candidate,
|
||||
&approval_entry,
|
||||
pending_all,
|
||||
).is_approved());
|
||||
assert!(check_approval(&candidate, &approval_entry, pending_all,).is_approved());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -631,15 +595,16 @@ mod tests {
|
||||
our_approval_sig: None,
|
||||
backing_group: GroupIndex(0),
|
||||
approved: false,
|
||||
}.into();
|
||||
}
|
||||
.into();
|
||||
|
||||
approval_entry.import_assignment(0,ValidatorIndex(0), block_tick);
|
||||
approval_entry.import_assignment(0,ValidatorIndex(1), block_tick);
|
||||
approval_entry.import_assignment(0, ValidatorIndex(0), block_tick);
|
||||
approval_entry.import_assignment(0, ValidatorIndex(1), block_tick);
|
||||
|
||||
approval_entry.import_assignment(1,ValidatorIndex(2), block_tick + 1);
|
||||
approval_entry.import_assignment(1,ValidatorIndex(3), block_tick + 1);
|
||||
approval_entry.import_assignment(1, ValidatorIndex(2), block_tick + 1);
|
||||
approval_entry.import_assignment(1, ValidatorIndex(3), block_tick + 1);
|
||||
|
||||
approval_entry.import_assignment(2,ValidatorIndex(4), block_tick + 2);
|
||||
approval_entry.import_assignment(2, ValidatorIndex(4), block_tick + 2);
|
||||
|
||||
let approvals = bitvec![BitOrderLsb0, u8; 1; 5];
|
||||
|
||||
@@ -669,7 +634,8 @@ mod tests {
|
||||
our_approval_sig: None,
|
||||
backing_group: GroupIndex(0),
|
||||
approved: false,
|
||||
}.into();
|
||||
}
|
||||
.into();
|
||||
|
||||
approval_entry.import_assignment(0, ValidatorIndex(0), block_tick);
|
||||
approval_entry.import_assignment(1, ValidatorIndex(2), block_tick);
|
||||
@@ -708,7 +674,8 @@ mod tests {
|
||||
our_approval_sig: None,
|
||||
backing_group: GroupIndex(0),
|
||||
approved: false,
|
||||
}.into();
|
||||
}
|
||||
.into();
|
||||
|
||||
approval_entry.import_assignment(0, ValidatorIndex(0), block_tick);
|
||||
approval_entry.import_assignment(0, ValidatorIndex(1), block_tick);
|
||||
@@ -752,7 +719,8 @@ mod tests {
|
||||
our_approval_sig: None,
|
||||
backing_group: GroupIndex(0),
|
||||
approved: false,
|
||||
}.into();
|
||||
}
|
||||
.into();
|
||||
|
||||
approval_entry.import_assignment(0, ValidatorIndex(0), block_tick);
|
||||
approval_entry.import_assignment(0, ValidatorIndex(1), block_tick);
|
||||
@@ -818,7 +786,8 @@ mod tests {
|
||||
our_approval_sig: None,
|
||||
backing_group: GroupIndex(0),
|
||||
approved: false,
|
||||
}.into();
|
||||
}
|
||||
.into();
|
||||
|
||||
approval_entry.import_assignment(0, ValidatorIndex(0), block_tick);
|
||||
approval_entry.import_assignment(0, ValidatorIndex(1), block_tick);
|
||||
@@ -868,7 +837,7 @@ mod tests {
|
||||
RequiredTranches::Exact {
|
||||
needed: 2,
|
||||
tolerated_missing: 1,
|
||||
next_no_show: Some(block_tick + 2*no_show_duration + 2),
|
||||
next_no_show: Some(block_tick + 2 * no_show_duration + 2),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -906,7 +875,8 @@ mod tests {
|
||||
our_approval_sig: None,
|
||||
backing_group: GroupIndex(0),
|
||||
approved: false,
|
||||
}.into();
|
||||
}
|
||||
.into();
|
||||
|
||||
approval_entry.import_assignment(0, ValidatorIndex(0), block_tick);
|
||||
approval_entry.import_assignment(0, ValidatorIndex(1), block_tick);
|
||||
@@ -935,11 +905,7 @@ mod tests {
|
||||
no_show_duration,
|
||||
needed_approvals,
|
||||
),
|
||||
RequiredTranches::Exact {
|
||||
needed: 2,
|
||||
tolerated_missing: 1,
|
||||
next_no_show: None,
|
||||
},
|
||||
RequiredTranches::Exact { needed: 2, tolerated_missing: 1, next_no_show: None },
|
||||
);
|
||||
|
||||
// Even though tranche 2 has 2 validators, it only covers 1 no-show.
|
||||
@@ -977,11 +943,7 @@ mod tests {
|
||||
no_show_duration,
|
||||
needed_approvals,
|
||||
),
|
||||
RequiredTranches::Exact {
|
||||
needed: 3,
|
||||
tolerated_missing: 2,
|
||||
next_no_show: None,
|
||||
},
|
||||
RequiredTranches::Exact { needed: 3, tolerated_missing: 2, next_no_show: None },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -996,7 +958,8 @@ mod tests {
|
||||
session: 0,
|
||||
block_assignments: Default::default(),
|
||||
approvals: bitvec![BitOrderLsb0, u8; 0; 3],
|
||||
}.into();
|
||||
}
|
||||
.into();
|
||||
|
||||
for i in 0..3 {
|
||||
candidate.mark_approval(ValidatorIndex(i));
|
||||
@@ -1015,7 +978,8 @@ mod tests {
|
||||
our_approval_sig: None,
|
||||
backing_group: GroupIndex(0),
|
||||
approved: false,
|
||||
}.into();
|
||||
}
|
||||
.into();
|
||||
|
||||
let approvals = bitvec![BitOrderLsb0, u8; 0; 3];
|
||||
|
||||
@@ -1043,14 +1007,14 @@ mod tests {
|
||||
const PREFIX: u32 = 10;
|
||||
|
||||
let test_tranches = vec![
|
||||
vec![], // empty set
|
||||
vec![0], // zero start
|
||||
vec![0, 3], // zero start with gap
|
||||
vec![2], // non-zero start
|
||||
vec![2, 4], // non-zero start with gap
|
||||
vec![0, 1, 2], // zero start with run and no gap
|
||||
vec![2, 3, 4, 8], // non-zero start with run and gap
|
||||
vec![0, 1, 2, 5, 6, 7], // zero start with runs and gap
|
||||
vec![], // empty set
|
||||
vec![0], // zero start
|
||||
vec![0, 3], // zero start with gap
|
||||
vec![2], // non-zero start
|
||||
vec![2, 4], // non-zero start with gap
|
||||
vec![0, 1, 2], // zero start with run and no gap
|
||||
vec![2, 3, 4, 8], // non-zero start with run and gap
|
||||
vec![0, 1, 2, 5, 6, 7], // zero start with runs and gap
|
||||
];
|
||||
|
||||
for test_tranche in test_tranches {
|
||||
@@ -1061,7 +1025,8 @@ mod tests {
|
||||
our_approval_sig: None,
|
||||
assignments: bitvec![BitOrderLsb0, u8; 0; 3],
|
||||
approved: false,
|
||||
}.into();
|
||||
}
|
||||
.into();
|
||||
|
||||
// Populate the requested tranches. The assignemnts aren't inspected in
|
||||
// this test.
|
||||
@@ -1072,10 +1037,8 @@ mod tests {
|
||||
let filled_tranches = filled_tranche_iterator(approval_entry.tranches());
|
||||
|
||||
// Take the first PREFIX entries and map them to their tranche.
|
||||
let tranches: Vec<DelayTranche> = filled_tranches
|
||||
.take(PREFIX as usize)
|
||||
.map(|e| e.0)
|
||||
.collect();
|
||||
let tranches: Vec<DelayTranche> =
|
||||
filled_tranches.take(PREFIX as usize).map(|e| e.0).collect();
|
||||
|
||||
// We expect this sequence to be sequential.
|
||||
let exp_tranches: Vec<DelayTranche> = (0..PREFIX).collect();
|
||||
@@ -1194,7 +1157,11 @@ mod tests {
|
||||
#[test]
|
||||
fn count_no_shows_three_validators_one_almost_late_one_no_show_one_approving() {
|
||||
test_count_no_shows(NoShowTest {
|
||||
assignments: vec![(ValidatorIndex(1), 21), (ValidatorIndex(2), 20), (ValidatorIndex(3), 20)],
|
||||
assignments: vec![
|
||||
(ValidatorIndex(1), 21),
|
||||
(ValidatorIndex(2), 20),
|
||||
(ValidatorIndex(3), 20),
|
||||
],
|
||||
approvals: vec![3],
|
||||
clock_drift: 10,
|
||||
no_show_duration: 10,
|
||||
@@ -1207,7 +1174,11 @@ mod tests {
|
||||
#[test]
|
||||
fn count_no_shows_three_no_show_validators() {
|
||||
test_count_no_shows(NoShowTest {
|
||||
assignments: vec![(ValidatorIndex(1), 20), (ValidatorIndex(2), 20), (ValidatorIndex(3), 20)],
|
||||
assignments: vec![
|
||||
(ValidatorIndex(1), 20),
|
||||
(ValidatorIndex(2), 20),
|
||||
(ValidatorIndex(3), 20),
|
||||
],
|
||||
approvals: vec![],
|
||||
clock_drift: 10,
|
||||
no_show_duration: 10,
|
||||
@@ -1220,7 +1191,11 @@ mod tests {
|
||||
#[test]
|
||||
fn count_no_shows_three_approving_validators() {
|
||||
test_count_no_shows(NoShowTest {
|
||||
assignments: vec![(ValidatorIndex(1), 20), (ValidatorIndex(2), 20), (ValidatorIndex(3), 20)],
|
||||
assignments: vec![
|
||||
(ValidatorIndex(1), 20),
|
||||
(ValidatorIndex(2), 20),
|
||||
(ValidatorIndex(3), 20),
|
||||
],
|
||||
approvals: vec![1, 2, 3],
|
||||
clock_drift: 10,
|
||||
no_show_duration: 10,
|
||||
@@ -1270,17 +1245,17 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn count_no_shows_validator_index_out_of_approvals_range_is_ignored_as_next_no_show() {
|
||||
test_count_no_shows(NoShowTest {
|
||||
assignments: vec![(ValidatorIndex(1000), 21)],
|
||||
approvals: vec![],
|
||||
clock_drift: 10,
|
||||
no_show_duration: 10,
|
||||
drifted_tick_now: 20,
|
||||
exp_no_shows: 0,
|
||||
exp_next_no_show: None,
|
||||
})
|
||||
}
|
||||
fn count_no_shows_validator_index_out_of_approvals_range_is_ignored_as_next_no_show() {
|
||||
test_count_no_shows(NoShowTest {
|
||||
assignments: vec![(ValidatorIndex(1000), 21)],
|
||||
approvals: vec![],
|
||||
clock_drift: 10,
|
||||
no_show_duration: 10,
|
||||
drifted_tick_now: 20,
|
||||
exp_no_shows: 0,
|
||||
exp_next_no_show: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1318,10 +1293,6 @@ fn depth_0_issued_as_exact_even_when_all() {
|
||||
|
||||
assert_eq!(
|
||||
state.output(0, 10, 10, 20),
|
||||
RequiredTranches::Exact {
|
||||
needed: 0,
|
||||
tolerated_missing: 0,
|
||||
next_no_show: None,
|
||||
},
|
||||
RequiredTranches::Exact { needed: 0, tolerated_missing: 0, next_no_show: None },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,21 +17,22 @@
|
||||
//! Version 1 of the DB schema.
|
||||
|
||||
use kvdb::{DBTransaction, KeyValueDB};
|
||||
use polkadot_node_subsystem::{SubsystemResult, SubsystemError};
|
||||
use polkadot_node_primitives::approval::{DelayTranche, AssignmentCert};
|
||||
use parity_scale_codec::{Decode, Encode};
|
||||
use polkadot_node_primitives::approval::{AssignmentCert, DelayTranche};
|
||||
use polkadot_node_subsystem::{SubsystemError, SubsystemResult};
|
||||
use polkadot_primitives::v1::{
|
||||
ValidatorIndex, GroupIndex, CandidateReceipt, SessionIndex, CoreIndex,
|
||||
BlockNumber, Hash, CandidateHash, ValidatorSignature,
|
||||
BlockNumber, CandidateHash, CandidateReceipt, CoreIndex, GroupIndex, Hash, SessionIndex,
|
||||
ValidatorIndex, ValidatorSignature,
|
||||
};
|
||||
use sp_consensus_slots::Slot;
|
||||
use parity_scale_codec::{Encode, Decode};
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
use bitvec::{vec::BitVec, order::Lsb0 as BitOrderLsb0};
|
||||
use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec};
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
|
||||
use crate::backend::{Backend, BackendWriteOp};
|
||||
use crate::persisted_entries;
|
||||
use crate::{
|
||||
backend::{Backend, BackendWriteOp},
|
||||
persisted_entries,
|
||||
};
|
||||
|
||||
const STORED_BLOCKS_KEY: &[u8] = b"Approvals_StoredBlocks";
|
||||
|
||||
@@ -48,10 +49,7 @@ impl DbBackend {
|
||||
/// Create a new [`DbBackend`] with the supplied key-value store and
|
||||
/// config.
|
||||
pub fn new(db: Arc<dyn KeyValueDB>, config: Config) -> Self {
|
||||
DbBackend {
|
||||
inner: db,
|
||||
config,
|
||||
}
|
||||
DbBackend { inner: db, config }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,22 +58,17 @@ impl Backend for DbBackend {
|
||||
&self,
|
||||
block_hash: &Hash,
|
||||
) -> SubsystemResult<Option<persisted_entries::BlockEntry>> {
|
||||
load_block_entry(&*self.inner, &self.config, block_hash)
|
||||
.map(|e| e.map(Into::into))
|
||||
load_block_entry(&*self.inner, &self.config, block_hash).map(|e| e.map(Into::into))
|
||||
}
|
||||
|
||||
fn load_candidate_entry(
|
||||
&self,
|
||||
candidate_hash: &CandidateHash,
|
||||
) -> SubsystemResult<Option<persisted_entries::CandidateEntry>> {
|
||||
load_candidate_entry(&*self.inner, &self.config, candidate_hash)
|
||||
.map(|e| e.map(Into::into))
|
||||
load_candidate_entry(&*self.inner, &self.config, candidate_hash).map(|e| e.map(Into::into))
|
||||
}
|
||||
|
||||
fn load_blocks_at_height(
|
||||
&self,
|
||||
block_height: &BlockNumber
|
||||
) -> SubsystemResult<Vec<Hash>> {
|
||||
fn load_blocks_at_height(&self, block_height: &BlockNumber) -> SubsystemResult<Vec<Hash>> {
|
||||
load_blocks_at_height(&*self.inner, &self.config, block_height)
|
||||
}
|
||||
|
||||
@@ -89,7 +82,8 @@ impl Backend for DbBackend {
|
||||
|
||||
/// Atomically write the list of operations, with later operations taking precedence over prior.
|
||||
fn write<I>(&mut self, ops: I) -> SubsystemResult<()>
|
||||
where I: IntoIterator<Item = BackendWriteOp>
|
||||
where
|
||||
I: IntoIterator<Item = BackendWriteOp>,
|
||||
{
|
||||
let mut tx = DBTransaction::new();
|
||||
for op in ops {
|
||||
@@ -100,20 +94,13 @@ impl Backend for DbBackend {
|
||||
&STORED_BLOCKS_KEY,
|
||||
stored_block_range.encode(),
|
||||
);
|
||||
}
|
||||
},
|
||||
BackendWriteOp::WriteBlocksAtHeight(h, blocks) => {
|
||||
tx.put_vec(
|
||||
self.config.col_data,
|
||||
&blocks_at_height_key(h),
|
||||
blocks.encode(),
|
||||
);
|
||||
}
|
||||
tx.put_vec(self.config.col_data, &blocks_at_height_key(h), blocks.encode());
|
||||
},
|
||||
BackendWriteOp::DeleteBlocksAtHeight(h) => {
|
||||
tx.delete(
|
||||
self.config.col_data,
|
||||
&blocks_at_height_key(h),
|
||||
);
|
||||
}
|
||||
tx.delete(self.config.col_data, &blocks_at_height_key(h));
|
||||
},
|
||||
BackendWriteOp::WriteBlockEntry(block_entry) => {
|
||||
let block_entry: BlockEntry = block_entry.into();
|
||||
tx.put_vec(
|
||||
@@ -121,13 +108,10 @@ impl Backend for DbBackend {
|
||||
&block_entry_key(&block_entry.block_hash),
|
||||
block_entry.encode(),
|
||||
);
|
||||
}
|
||||
},
|
||||
BackendWriteOp::DeleteBlockEntry(hash) => {
|
||||
tx.delete(
|
||||
self.config.col_data,
|
||||
&block_entry_key(&hash),
|
||||
);
|
||||
}
|
||||
tx.delete(self.config.col_data, &block_entry_key(&hash));
|
||||
},
|
||||
BackendWriteOp::WriteCandidateEntry(candidate_entry) => {
|
||||
let candidate_entry: CandidateEntry = candidate_entry.into();
|
||||
tx.put_vec(
|
||||
@@ -135,13 +119,10 @@ impl Backend for DbBackend {
|
||||
&candidate_entry_key(&candidate_entry.candidate.hash()),
|
||||
candidate_entry.encode(),
|
||||
);
|
||||
}
|
||||
},
|
||||
BackendWriteOp::DeleteCandidateEntry(candidate_hash) => {
|
||||
tx.delete(
|
||||
self.config.col_data,
|
||||
&candidate_entry_key(&candidate_hash),
|
||||
);
|
||||
}
|
||||
tx.delete(self.config.col_data, &candidate_entry_key(&candidate_hash));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,13 +238,14 @@ impl std::error::Error for Error {}
|
||||
/// Result alias for DB errors.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
pub(crate) fn load_decode<D: Decode>(store: &dyn KeyValueDB, col_data: u32, key: &[u8]) -> Result<Option<D>>
|
||||
{
|
||||
pub(crate) fn load_decode<D: Decode>(
|
||||
store: &dyn KeyValueDB,
|
||||
col_data: u32,
|
||||
key: &[u8],
|
||||
) -> Result<Option<D>> {
|
||||
match store.get(col_data, key)? {
|
||||
None => Ok(None),
|
||||
Some(raw) => D::decode(&mut &raw[..])
|
||||
.map(Some)
|
||||
.map_err(Into::into),
|
||||
Some(raw) => D::decode(&mut &raw[..]).map(Some).map_err(Into::into),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +290,6 @@ pub fn load_all_blocks(store: &dyn KeyValueDB, config: &Config) -> SubsystemResu
|
||||
let blocks = load_blocks_at_height(store, config, &height)?;
|
||||
hashes.extend(blocks);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Ok(hashes)
|
||||
|
||||
@@ -16,21 +16,19 @@
|
||||
|
||||
//! Tests for the aux-schema of approval voting.
|
||||
|
||||
use super::*;
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
use polkadot_primitives::v1::Id as ParaId;
|
||||
use super::{DbBackend, StoredBlockRange, *};
|
||||
use crate::{
|
||||
backend::{Backend, OverlayedBackend},
|
||||
ops::{add_block_entry, canonicalize, force_approve, NewCandidateInfo},
|
||||
};
|
||||
use kvdb::KeyValueDB;
|
||||
use super::{DbBackend, StoredBlockRange};
|
||||
use crate::backend::{Backend, OverlayedBackend};
|
||||
use crate::ops::{NewCandidateInfo, add_block_entry, force_approve, canonicalize};
|
||||
use polkadot_primitives::v1::Id as ParaId;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
const DATA_COL: u32 = 0;
|
||||
const NUM_COLUMNS: u32 = 1;
|
||||
|
||||
const TEST_CONFIG: Config = Config {
|
||||
col_data: DATA_COL,
|
||||
};
|
||||
const TEST_CONFIG: Config = Config { col_data: DATA_COL };
|
||||
|
||||
fn make_db() -> (DbBackend, Arc<dyn KeyValueDB>) {
|
||||
let db_writer: Arc<dyn KeyValueDB> = Arc::new(kvdb_memorydb::create(NUM_COLUMNS));
|
||||
@@ -80,26 +78,25 @@ fn read_write() {
|
||||
let range = StoredBlockRange(10, 20);
|
||||
let at_height = vec![hash_a, hash_b];
|
||||
|
||||
let block_entry = make_block_entry(
|
||||
hash_a,
|
||||
Default::default(),
|
||||
1,
|
||||
vec![(CoreIndex(0), candidate_hash)],
|
||||
);
|
||||
let block_entry =
|
||||
make_block_entry(hash_a, Default::default(), 1, vec![(CoreIndex(0), candidate_hash)]);
|
||||
|
||||
let candidate_entry = CandidateEntry {
|
||||
candidate: Default::default(),
|
||||
session: 5,
|
||||
block_assignments: vec![
|
||||
(hash_a, ApprovalEntry {
|
||||
block_assignments: vec![(
|
||||
hash_a,
|
||||
ApprovalEntry {
|
||||
tranches: Vec::new(),
|
||||
backing_group: GroupIndex(1),
|
||||
our_assignment: None,
|
||||
our_approval_sig: None,
|
||||
assignments: Default::default(),
|
||||
approved: false,
|
||||
})
|
||||
].into_iter().collect(),
|
||||
},
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
approvals: Default::default(),
|
||||
};
|
||||
|
||||
@@ -114,7 +111,10 @@ fn read_write() {
|
||||
|
||||
assert_eq!(load_stored_blocks(store.as_ref(), &TEST_CONFIG).unwrap(), Some(range));
|
||||
assert_eq!(load_blocks_at_height(store.as_ref(), &TEST_CONFIG, &1).unwrap(), at_height);
|
||||
assert_eq!(load_block_entry(store.as_ref(), &TEST_CONFIG, &hash_a).unwrap(), Some(block_entry.into()));
|
||||
assert_eq!(
|
||||
load_block_entry(store.as_ref(), &TEST_CONFIG, &hash_a).unwrap(),
|
||||
Some(block_entry.into())
|
||||
);
|
||||
assert_eq!(
|
||||
load_candidate_entry(store.as_ref(), &TEST_CONFIG, &candidate_hash).unwrap(),
|
||||
Some(candidate_entry.into()),
|
||||
@@ -129,7 +129,9 @@ fn read_write() {
|
||||
|
||||
assert!(load_blocks_at_height(store.as_ref(), &TEST_CONFIG, &1).unwrap().is_empty());
|
||||
assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &hash_a).unwrap().is_none());
|
||||
assert!(load_candidate_entry(store.as_ref(), &TEST_CONFIG, &candidate_hash).unwrap().is_none());
|
||||
assert!(load_candidate_entry(store.as_ref(), &TEST_CONFIG, &candidate_hash)
|
||||
.unwrap()
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -165,47 +167,48 @@ fn add_block_entry_works() {
|
||||
let n_validators = 10;
|
||||
|
||||
let mut new_candidate_info = HashMap::new();
|
||||
new_candidate_info.insert(candidate_hash_a, NewCandidateInfo::new(
|
||||
candidate_receipt_a,
|
||||
GroupIndex(0),
|
||||
None,
|
||||
));
|
||||
new_candidate_info
|
||||
.insert(candidate_hash_a, NewCandidateInfo::new(candidate_receipt_a, GroupIndex(0), None));
|
||||
|
||||
let mut overlay_db = OverlayedBackend::new(&db);
|
||||
add_block_entry(
|
||||
&mut overlay_db,
|
||||
block_entry_a.clone().into(),
|
||||
n_validators,
|
||||
|h| new_candidate_info.get(h).map(|x| x.clone()),
|
||||
).unwrap();
|
||||
add_block_entry(&mut overlay_db, block_entry_a.clone().into(), n_validators, |h| {
|
||||
new_candidate_info.get(h).map(|x| x.clone())
|
||||
})
|
||||
.unwrap();
|
||||
let write_ops = overlay_db.into_write_ops();
|
||||
db.write(write_ops).unwrap();
|
||||
|
||||
new_candidate_info.insert(candidate_hash_b, NewCandidateInfo::new(
|
||||
candidate_receipt_b,
|
||||
GroupIndex(1),
|
||||
None,
|
||||
));
|
||||
new_candidate_info
|
||||
.insert(candidate_hash_b, NewCandidateInfo::new(candidate_receipt_b, GroupIndex(1), None));
|
||||
|
||||
let mut overlay_db = OverlayedBackend::new(&db);
|
||||
add_block_entry(
|
||||
&mut overlay_db,
|
||||
block_entry_b.clone().into(),
|
||||
n_validators,
|
||||
|h| new_candidate_info.get(h).map(|x| x.clone()),
|
||||
).unwrap();
|
||||
add_block_entry(&mut overlay_db, block_entry_b.clone().into(), n_validators, |h| {
|
||||
new_candidate_info.get(h).map(|x| x.clone())
|
||||
})
|
||||
.unwrap();
|
||||
let write_ops = overlay_db.into_write_ops();
|
||||
db.write(write_ops).unwrap();
|
||||
|
||||
assert_eq!(load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_a).unwrap(), Some(block_entry_a.into()));
|
||||
assert_eq!(load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_b).unwrap(), Some(block_entry_b.into()));
|
||||
assert_eq!(
|
||||
load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_a).unwrap(),
|
||||
Some(block_entry_a.into())
|
||||
);
|
||||
assert_eq!(
|
||||
load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_b).unwrap(),
|
||||
Some(block_entry_b.into())
|
||||
);
|
||||
|
||||
let candidate_entry_a = load_candidate_entry(store.as_ref(), &TEST_CONFIG, &candidate_hash_a)
|
||||
.unwrap().unwrap();
|
||||
assert_eq!(candidate_entry_a.block_assignments.keys().collect::<Vec<_>>(), vec![&block_hash_a, &block_hash_b]);
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
candidate_entry_a.block_assignments.keys().collect::<Vec<_>>(),
|
||||
vec![&block_hash_a, &block_hash_b]
|
||||
);
|
||||
|
||||
let candidate_entry_b = load_candidate_entry(store.as_ref(), &TEST_CONFIG, &candidate_hash_b)
|
||||
.unwrap().unwrap();
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(candidate_entry_b.block_assignments.keys().collect::<Vec<_>>(), vec![&block_hash_b]);
|
||||
}
|
||||
|
||||
@@ -217,44 +220,30 @@ fn add_block_entry_adds_child() {
|
||||
let block_hash_a = Hash::repeat_byte(2);
|
||||
let block_hash_b = Hash::repeat_byte(69);
|
||||
|
||||
let mut block_entry_a = make_block_entry(
|
||||
block_hash_a,
|
||||
parent_hash,
|
||||
1,
|
||||
Vec::new(),
|
||||
);
|
||||
let mut block_entry_a = make_block_entry(block_hash_a, parent_hash, 1, Vec::new());
|
||||
|
||||
let block_entry_b = make_block_entry(
|
||||
block_hash_b,
|
||||
block_hash_a,
|
||||
2,
|
||||
Vec::new(),
|
||||
);
|
||||
let block_entry_b = make_block_entry(block_hash_b, block_hash_a, 2, Vec::new());
|
||||
|
||||
let n_validators = 10;
|
||||
|
||||
let mut overlay_db = OverlayedBackend::new(&db);
|
||||
add_block_entry(
|
||||
&mut overlay_db,
|
||||
block_entry_a.clone().into(),
|
||||
n_validators,
|
||||
|_| None,
|
||||
).unwrap();
|
||||
add_block_entry(&mut overlay_db, block_entry_a.clone().into(), n_validators, |_| None).unwrap();
|
||||
|
||||
add_block_entry(
|
||||
&mut overlay_db,
|
||||
block_entry_b.clone().into(),
|
||||
n_validators,
|
||||
|_| None,
|
||||
).unwrap();
|
||||
add_block_entry(&mut overlay_db, block_entry_b.clone().into(), n_validators, |_| None).unwrap();
|
||||
|
||||
let write_ops = overlay_db.into_write_ops();
|
||||
db.write(write_ops).unwrap();
|
||||
|
||||
block_entry_a.children.push(block_hash_b);
|
||||
|
||||
assert_eq!(load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_a).unwrap(), Some(block_entry_a.into()));
|
||||
assert_eq!(load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_b).unwrap(), Some(block_entry_b.into()));
|
||||
assert_eq!(
|
||||
load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_a).unwrap(),
|
||||
Some(block_entry_a.into())
|
||||
);
|
||||
assert_eq!(
|
||||
load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_b).unwrap(),
|
||||
Some(block_entry_b.into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -305,12 +294,8 @@ fn canonicalize_works() {
|
||||
|
||||
let block_entry_a = make_block_entry(block_hash_a, genesis, 1, Vec::new());
|
||||
let block_entry_b1 = make_block_entry(block_hash_b1, block_hash_a, 2, Vec::new());
|
||||
let block_entry_b2 = make_block_entry(
|
||||
block_hash_b2,
|
||||
block_hash_a,
|
||||
2,
|
||||
vec![(CoreIndex(0), cand_hash_1)],
|
||||
);
|
||||
let block_entry_b2 =
|
||||
make_block_entry(block_hash_b2, block_hash_a, 2, vec![(CoreIndex(0), cand_hash_1)]);
|
||||
let block_entry_c1 = make_block_entry(block_hash_c1, block_hash_b1, 3, Vec::new());
|
||||
let block_entry_c2 = make_block_entry(
|
||||
block_hash_c2,
|
||||
@@ -324,44 +309,27 @@ fn canonicalize_works() {
|
||||
4,
|
||||
vec![(CoreIndex(0), cand_hash_3), (CoreIndex(1), cand_hash_4)],
|
||||
);
|
||||
let block_entry_d2 = make_block_entry(
|
||||
block_hash_d2,
|
||||
block_hash_c2,
|
||||
4,
|
||||
vec![(CoreIndex(0), cand_hash_5)],
|
||||
);
|
||||
let block_entry_d2 =
|
||||
make_block_entry(block_hash_d2, block_hash_c2, 4, vec![(CoreIndex(0), cand_hash_5)]);
|
||||
|
||||
let candidate_info = {
|
||||
let mut candidate_info = HashMap::new();
|
||||
candidate_info.insert(cand_hash_1, NewCandidateInfo::new(
|
||||
candidate_receipt_genesis,
|
||||
GroupIndex(1),
|
||||
None,
|
||||
));
|
||||
candidate_info.insert(
|
||||
cand_hash_1,
|
||||
NewCandidateInfo::new(candidate_receipt_genesis, GroupIndex(1), None),
|
||||
);
|
||||
|
||||
candidate_info.insert(cand_hash_2, NewCandidateInfo::new(
|
||||
candidate_receipt_a,
|
||||
GroupIndex(2),
|
||||
None,
|
||||
));
|
||||
candidate_info
|
||||
.insert(cand_hash_2, NewCandidateInfo::new(candidate_receipt_a, GroupIndex(2), None));
|
||||
|
||||
candidate_info.insert(cand_hash_3, NewCandidateInfo::new(
|
||||
candidate_receipt_b,
|
||||
GroupIndex(3),
|
||||
None,
|
||||
));
|
||||
candidate_info
|
||||
.insert(cand_hash_3, NewCandidateInfo::new(candidate_receipt_b, GroupIndex(3), None));
|
||||
|
||||
candidate_info.insert(cand_hash_4, NewCandidateInfo::new(
|
||||
candidate_receipt_b1,
|
||||
GroupIndex(4),
|
||||
None,
|
||||
));
|
||||
candidate_info
|
||||
.insert(cand_hash_4, NewCandidateInfo::new(candidate_receipt_b1, GroupIndex(4), None));
|
||||
|
||||
candidate_info.insert(cand_hash_5, NewCandidateInfo::new(
|
||||
candidate_receipt_c1,
|
||||
GroupIndex(5),
|
||||
None,
|
||||
));
|
||||
candidate_info
|
||||
.insert(cand_hash_5, NewCandidateInfo::new(candidate_receipt_c1, GroupIndex(5), None));
|
||||
|
||||
candidate_info
|
||||
};
|
||||
@@ -379,12 +347,10 @@ fn canonicalize_works() {
|
||||
|
||||
let mut overlay_db = OverlayedBackend::new(&db);
|
||||
for block_entry in blocks {
|
||||
add_block_entry(
|
||||
&mut overlay_db,
|
||||
block_entry.into(),
|
||||
n_validators,
|
||||
|h| candidate_info.get(h).map(|x| x.clone()),
|
||||
).unwrap();
|
||||
add_block_entry(&mut overlay_db, block_entry.into(), n_validators, |h| {
|
||||
candidate_info.get(h).map(|x| x.clone())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
let write_ops = overlay_db.into_write_ops();
|
||||
db.write(write_ops).unwrap();
|
||||
@@ -393,9 +359,11 @@ fn canonicalize_works() {
|
||||
for (c_hash, in_blocks) in expected {
|
||||
let (entry, in_blocks) = match in_blocks {
|
||||
None => {
|
||||
assert!(load_candidate_entry(store.as_ref(), &TEST_CONFIG, &c_hash).unwrap().is_none());
|
||||
assert!(load_candidate_entry(store.as_ref(), &TEST_CONFIG, &c_hash)
|
||||
.unwrap()
|
||||
.is_none());
|
||||
continue
|
||||
}
|
||||
},
|
||||
Some(i) => (
|
||||
load_candidate_entry(store.as_ref(), &TEST_CONFIG, &c_hash).unwrap().unwrap(),
|
||||
i,
|
||||
@@ -414,13 +382,13 @@ fn canonicalize_works() {
|
||||
for (hash, with_candidates) in expected {
|
||||
let (entry, with_candidates) = match with_candidates {
|
||||
None => {
|
||||
assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &hash).unwrap().is_none());
|
||||
assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &hash)
|
||||
.unwrap()
|
||||
.is_none());
|
||||
continue
|
||||
}
|
||||
Some(i) => (
|
||||
load_block_entry(store.as_ref(), &TEST_CONFIG, &hash).unwrap().unwrap(),
|
||||
i,
|
||||
),
|
||||
},
|
||||
Some(i) =>
|
||||
(load_block_entry(store.as_ref(), &TEST_CONFIG, &hash).unwrap().unwrap(), i),
|
||||
};
|
||||
|
||||
assert_eq!(entry.candidates.len(), with_candidates.len());
|
||||
@@ -454,7 +422,10 @@ fn canonicalize_works() {
|
||||
let write_ops = overlay_db.into_write_ops();
|
||||
db.write(write_ops).unwrap();
|
||||
|
||||
assert_eq!(load_stored_blocks(store.as_ref(), &TEST_CONFIG).unwrap().unwrap(), StoredBlockRange(4, 5));
|
||||
assert_eq!(
|
||||
load_stored_blocks(store.as_ref(), &TEST_CONFIG).unwrap().unwrap(),
|
||||
StoredBlockRange(4, 5)
|
||||
);
|
||||
|
||||
check_candidates_in_store(vec![
|
||||
(cand_hash_1, None),
|
||||
@@ -489,25 +460,31 @@ fn force_approve_works() {
|
||||
let single_candidate_vec = vec![(CoreIndex(0), candidate_hash)];
|
||||
let candidate_info = {
|
||||
let mut candidate_info = HashMap::new();
|
||||
candidate_info.insert(candidate_hash, NewCandidateInfo::new(
|
||||
make_candidate(1.into(), Default::default()),
|
||||
GroupIndex(1),
|
||||
None,
|
||||
));
|
||||
candidate_info.insert(
|
||||
candidate_hash,
|
||||
NewCandidateInfo::new(
|
||||
make_candidate(1.into(), Default::default()),
|
||||
GroupIndex(1),
|
||||
None,
|
||||
),
|
||||
);
|
||||
|
||||
candidate_info
|
||||
};
|
||||
|
||||
|
||||
let block_hash_a = Hash::repeat_byte(1); // 1
|
||||
let block_hash_b = Hash::repeat_byte(2);
|
||||
let block_hash_c = Hash::repeat_byte(3);
|
||||
let block_hash_d = Hash::repeat_byte(4); // 4
|
||||
|
||||
let block_entry_a = make_block_entry(block_hash_a, Default::default(), 1, single_candidate_vec.clone());
|
||||
let block_entry_b = make_block_entry(block_hash_b, block_hash_a, 2, single_candidate_vec.clone());
|
||||
let block_entry_c = make_block_entry(block_hash_c, block_hash_b, 3, single_candidate_vec.clone());
|
||||
let block_entry_d = make_block_entry(block_hash_d, block_hash_c, 4, single_candidate_vec.clone());
|
||||
let block_entry_a =
|
||||
make_block_entry(block_hash_a, Default::default(), 1, single_candidate_vec.clone());
|
||||
let block_entry_b =
|
||||
make_block_entry(block_hash_b, block_hash_a, 2, single_candidate_vec.clone());
|
||||
let block_entry_c =
|
||||
make_block_entry(block_hash_c, block_hash_b, 3, single_candidate_vec.clone());
|
||||
let block_entry_d =
|
||||
make_block_entry(block_hash_d, block_hash_c, 4, single_candidate_vec.clone());
|
||||
|
||||
let blocks = vec![
|
||||
block_entry_a.clone(),
|
||||
@@ -518,41 +495,36 @@ fn force_approve_works() {
|
||||
|
||||
let mut overlay_db = OverlayedBackend::new(&db);
|
||||
for block_entry in blocks {
|
||||
add_block_entry(
|
||||
&mut overlay_db,
|
||||
block_entry.into(),
|
||||
n_validators,
|
||||
|h| candidate_info.get(h).map(|x| x.clone()),
|
||||
).unwrap();
|
||||
add_block_entry(&mut overlay_db, block_entry.into(), n_validators, |h| {
|
||||
candidate_info.get(h).map(|x| x.clone())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
let approved_hashes = force_approve(&mut overlay_db, block_hash_d, 2).unwrap();
|
||||
let approved_hashes = force_approve(&mut overlay_db, block_hash_d, 2).unwrap();
|
||||
let write_ops = overlay_db.into_write_ops();
|
||||
db.write(write_ops).unwrap();
|
||||
|
||||
assert!(load_block_entry(
|
||||
store.as_ref(),
|
||||
&TEST_CONFIG,
|
||||
&block_hash_a,
|
||||
).unwrap().unwrap().approved_bitfield.all());
|
||||
assert!(load_block_entry(
|
||||
store.as_ref(),
|
||||
&TEST_CONFIG,
|
||||
&block_hash_b,
|
||||
).unwrap().unwrap().approved_bitfield.all());
|
||||
assert!(load_block_entry(
|
||||
store.as_ref(),
|
||||
&TEST_CONFIG,
|
||||
&block_hash_c,
|
||||
).unwrap().unwrap().approved_bitfield.not_any());
|
||||
assert!(load_block_entry(
|
||||
store.as_ref(),
|
||||
&TEST_CONFIG,
|
||||
&block_hash_d,
|
||||
).unwrap().unwrap().approved_bitfield.not_any());
|
||||
assert_eq!(
|
||||
approved_hashes,
|
||||
vec![block_hash_b, block_hash_a],
|
||||
);
|
||||
assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_a,)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.approved_bitfield
|
||||
.all());
|
||||
assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_b,)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.approved_bitfield
|
||||
.all());
|
||||
assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_c,)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.approved_bitfield
|
||||
.not_any());
|
||||
assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_d,)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.approved_bitfield
|
||||
.not_any());
|
||||
assert_eq!(approved_hashes, vec![block_hash_b, block_hash_a],);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -566,60 +538,27 @@ fn load_all_blocks_works() {
|
||||
|
||||
let block_number = 10;
|
||||
|
||||
let block_entry_a = make_block_entry(
|
||||
block_hash_a,
|
||||
parent_hash,
|
||||
block_number,
|
||||
vec![],
|
||||
);
|
||||
let block_entry_a = make_block_entry(block_hash_a, parent_hash, block_number, vec![]);
|
||||
|
||||
let block_entry_b = make_block_entry(
|
||||
block_hash_b,
|
||||
parent_hash,
|
||||
block_number,
|
||||
vec![],
|
||||
);
|
||||
let block_entry_b = make_block_entry(block_hash_b, parent_hash, block_number, vec![]);
|
||||
|
||||
let block_entry_c = make_block_entry(
|
||||
block_hash_c,
|
||||
block_hash_a,
|
||||
block_number + 1,
|
||||
vec![],
|
||||
);
|
||||
let block_entry_c = make_block_entry(block_hash_c, block_hash_a, block_number + 1, vec![]);
|
||||
|
||||
let n_validators = 10;
|
||||
|
||||
let mut overlay_db = OverlayedBackend::new(&db);
|
||||
add_block_entry(
|
||||
&mut overlay_db,
|
||||
block_entry_a.clone().into(),
|
||||
n_validators,
|
||||
|_| None
|
||||
).unwrap();
|
||||
add_block_entry(&mut overlay_db, block_entry_a.clone().into(), n_validators, |_| None).unwrap();
|
||||
|
||||
// add C before B to test sorting.
|
||||
add_block_entry(
|
||||
&mut overlay_db,
|
||||
block_entry_c.clone().into(),
|
||||
n_validators,
|
||||
|_| None
|
||||
).unwrap();
|
||||
add_block_entry(&mut overlay_db, block_entry_c.clone().into(), n_validators, |_| None).unwrap();
|
||||
|
||||
add_block_entry(
|
||||
&mut overlay_db,
|
||||
block_entry_b.clone().into(),
|
||||
n_validators,
|
||||
|_| None
|
||||
).unwrap();
|
||||
add_block_entry(&mut overlay_db, block_entry_b.clone().into(), n_validators, |_| None).unwrap();
|
||||
|
||||
let write_ops = overlay_db.into_write_ops();
|
||||
db.write(write_ops).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
load_all_blocks(
|
||||
store.as_ref(),
|
||||
&TEST_CONFIG
|
||||
).unwrap(),
|
||||
load_all_blocks(store.as_ref(), &TEST_CONFIG).unwrap(),
|
||||
vec![block_hash_a, block_hash_b, block_hash_c],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -21,13 +21,15 @@
|
||||
//! [`Backend`], maintaining consistency between queries and temporary writes,
|
||||
//! before any commit to the underlying storage is made.
|
||||
|
||||
use polkadot_node_subsystem::{SubsystemResult};
|
||||
use polkadot_node_subsystem::SubsystemResult;
|
||||
use polkadot_primitives::v1::{BlockNumber, CandidateHash, Hash};
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::approval_db::v1::StoredBlockRange;
|
||||
use super::persisted_entries::{BlockEntry, CandidateEntry};
|
||||
use super::{
|
||||
approval_db::v1::StoredBlockRange,
|
||||
persisted_entries::{BlockEntry, CandidateEntry},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum BackendWriteOp {
|
||||
@@ -45,7 +47,10 @@ pub trait Backend {
|
||||
/// Load a block entry from the DB.
|
||||
fn load_block_entry(&self, hash: &Hash) -> SubsystemResult<Option<BlockEntry>>;
|
||||
/// Load a candidate entry from the DB.
|
||||
fn load_candidate_entry(&self, candidate_hash: &CandidateHash) -> SubsystemResult<Option<CandidateEntry>>;
|
||||
fn load_candidate_entry(
|
||||
&self,
|
||||
candidate_hash: &CandidateHash,
|
||||
) -> SubsystemResult<Option<CandidateEntry>>;
|
||||
/// Load all blocks at a specific height.
|
||||
fn load_blocks_at_height(&self, height: &BlockNumber) -> SubsystemResult<Vec<Hash>>;
|
||||
/// Load all block from the DB.
|
||||
@@ -54,7 +59,8 @@ pub trait Backend {
|
||||
fn load_stored_blocks(&self) -> SubsystemResult<Option<StoredBlockRange>>;
|
||||
/// Atomically write the list of operations, with later operations taking precedence over prior.
|
||||
fn write<I>(&mut self, ops: I) -> SubsystemResult<()>
|
||||
where I: IntoIterator<Item = BackendWriteOp>;
|
||||
where
|
||||
I: IntoIterator<Item = BackendWriteOp>;
|
||||
}
|
||||
|
||||
/// An in-memory overlay over the backend.
|
||||
@@ -128,7 +134,10 @@ impl<'a, B: 'a + Backend> OverlayedBackend<'a, B> {
|
||||
self.inner.load_block_entry(hash)
|
||||
}
|
||||
|
||||
pub fn load_candidate_entry(&self, candidate_hash: &CandidateHash) -> SubsystemResult<Option<CandidateEntry>> {
|
||||
pub fn load_candidate_entry(
|
||||
&self,
|
||||
candidate_hash: &CandidateHash,
|
||||
) -> SubsystemResult<Option<CandidateEntry>> {
|
||||
if let Some(val) = self.candidate_entries.get(&candidate_hash) {
|
||||
return Ok(val.clone())
|
||||
}
|
||||
|
||||
@@ -16,21 +16,20 @@
|
||||
|
||||
//! Assignment criteria VRF generation and checking.
|
||||
|
||||
use parity_scale_codec::{Decode, Encode};
|
||||
use polkadot_node_primitives::approval::{
|
||||
self as approval_types, AssignmentCert, AssignmentCertKind, DelayTranche, RelayVRFStory,
|
||||
};
|
||||
use polkadot_primitives::v1::{
|
||||
CoreIndex, ValidatorIndex, SessionInfo, AssignmentPair, AssignmentId, GroupIndex, CandidateHash,
|
||||
AssignmentId, AssignmentPair, CandidateHash, CoreIndex, GroupIndex, SessionInfo, ValidatorIndex,
|
||||
};
|
||||
use sc_keystore::LocalKeystore;
|
||||
use parity_scale_codec::{Encode, Decode};
|
||||
use sp_application_crypto::Public;
|
||||
|
||||
use merlin::Transcript;
|
||||
use schnorrkel::vrf::VRFInOut;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::{hash_map::Entry, HashMap};
|
||||
|
||||
use super::LOG_TARGET;
|
||||
|
||||
@@ -88,10 +87,7 @@ impl From<OurAssignment> for crate::approval_db::v1::OurAssignment {
|
||||
}
|
||||
}
|
||||
|
||||
fn relay_vrf_modulo_transcript(
|
||||
relay_vrf_story: RelayVRFStory,
|
||||
sample: u32,
|
||||
) -> Transcript {
|
||||
fn relay_vrf_modulo_transcript(relay_vrf_story: RelayVRFStory, sample: u32) -> Transcript {
|
||||
// combine the relay VRF story with a sample number.
|
||||
let mut t = Transcript::new(approval_types::RELAY_VRF_MODULO_CONTEXT);
|
||||
t.append_message(b"RC-VRF", &relay_vrf_story.0);
|
||||
@@ -100,10 +96,7 @@ fn relay_vrf_modulo_transcript(
|
||||
t
|
||||
}
|
||||
|
||||
fn relay_vrf_modulo_core(
|
||||
vrf_in_out: &VRFInOut,
|
||||
n_cores: u32,
|
||||
) -> CoreIndex {
|
||||
fn relay_vrf_modulo_core(vrf_in_out: &VRFInOut, n_cores: u32) -> CoreIndex {
|
||||
let bytes: [u8; 4] = vrf_in_out.make_bytes(approval_types::CORE_RANDOMNESS_CONTEXT);
|
||||
|
||||
// interpret as little-endian u32.
|
||||
@@ -111,10 +104,7 @@ fn relay_vrf_modulo_core(
|
||||
CoreIndex(random_core)
|
||||
}
|
||||
|
||||
fn relay_vrf_delay_transcript(
|
||||
relay_vrf_story: RelayVRFStory,
|
||||
core_index: CoreIndex,
|
||||
) -> Transcript {
|
||||
fn relay_vrf_delay_transcript(relay_vrf_story: RelayVRFStory, core_index: CoreIndex) -> Transcript {
|
||||
let mut t = Transcript::new(approval_types::RELAY_VRF_DELAY_CONTEXT);
|
||||
t.append_message(b"RC-VRF", &relay_vrf_story.0);
|
||||
core_index.0.using_encoded(|s| t.append_message(b"core", s));
|
||||
@@ -129,7 +119,8 @@ fn relay_vrf_delay_tranche(
|
||||
let bytes: [u8; 4] = vrf_in_out.make_bytes(approval_types::TRANCHE_RANDOMNESS_CONTEXT);
|
||||
|
||||
// interpret as little-endian u32 and reduce by the number of tranches.
|
||||
let wide_tranche = u32::from_le_bytes(bytes) % (num_delay_tranches + zeroth_delay_tranche_width);
|
||||
let wide_tranche =
|
||||
u32::from_le_bytes(bytes) % (num_delay_tranches + zeroth_delay_tranche_width);
|
||||
|
||||
// Consolidate early results to tranche zero so tranche zero is extra wide.
|
||||
wide_tranche.saturating_sub(zeroth_delay_tranche_width)
|
||||
@@ -202,12 +193,7 @@ impl AssignmentCriteria for RealAssignmentCriteria {
|
||||
config: &Config,
|
||||
leaving_cores: Vec<(CandidateHash, CoreIndex, GroupIndex)>,
|
||||
) -> HashMap<CoreIndex, OurAssignment> {
|
||||
compute_assignments(
|
||||
keystore,
|
||||
relay_vrf_story,
|
||||
config,
|
||||
leaving_cores,
|
||||
)
|
||||
compute_assignments(keystore, relay_vrf_story, config, leaving_cores)
|
||||
}
|
||||
|
||||
fn check_assignment_cert(
|
||||
@@ -246,13 +232,16 @@ pub(crate) fn compute_assignments(
|
||||
config: &Config,
|
||||
leaving_cores: impl IntoIterator<Item = (CandidateHash, CoreIndex, GroupIndex)> + Clone,
|
||||
) -> HashMap<CoreIndex, OurAssignment> {
|
||||
if config.n_cores == 0 || config.assignment_keys.is_empty() || config.validator_groups.is_empty() {
|
||||
if config.n_cores == 0 ||
|
||||
config.assignment_keys.is_empty() ||
|
||||
config.validator_groups.is_empty()
|
||||
{
|
||||
return HashMap::new()
|
||||
}
|
||||
|
||||
let (index, assignments_key): (ValidatorIndex, AssignmentPair) = {
|
||||
let key = config.assignment_keys.iter().enumerate()
|
||||
.find_map(|(i, p)| match keystore.key_pair(p) {
|
||||
let key = config.assignment_keys.iter().enumerate().find_map(|(i, p)| {
|
||||
match keystore.key_pair(p) {
|
||||
Ok(Some(pair)) => Some((ValidatorIndex(i as _), pair)),
|
||||
Ok(None) => None,
|
||||
Err(sc_keystore::Error::Unavailable) => None,
|
||||
@@ -260,8 +249,9 @@ pub(crate) fn compute_assignments(
|
||||
Err(e) => {
|
||||
tracing::warn!(target: LOG_TARGET, "Encountered keystore error: {:?}", e);
|
||||
None
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
match key {
|
||||
None => return Default::default(),
|
||||
@@ -270,7 +260,8 @@ pub(crate) fn compute_assignments(
|
||||
};
|
||||
|
||||
// Ignore any cores where the assigned group is our own.
|
||||
let leaving_cores = leaving_cores.into_iter()
|
||||
let leaving_cores = leaving_cores
|
||||
.into_iter()
|
||||
.filter(|&(_, _, ref g)| !is_in_backing_group(&config.validator_groups, index, *g))
|
||||
.map(|(c_hash, core, _)| (c_hash, core))
|
||||
.collect::<Vec<_>>();
|
||||
@@ -322,8 +313,8 @@ fn compute_relay_vrf_modulo_assignments(
|
||||
relay_vrf_modulo_transcript(relay_vrf_story.clone(), rvm_sample),
|
||||
|vrf_in_out| {
|
||||
*core = relay_vrf_modulo_core(&vrf_in_out, config.n_cores);
|
||||
if let Some((candidate_hash, _))
|
||||
= leaving_cores.clone().into_iter().find(|(_, c)| c == core)
|
||||
if let Some((candidate_hash, _)) =
|
||||
leaving_cores.clone().into_iter().find(|(_, c)| c == core)
|
||||
{
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
@@ -338,7 +329,7 @@ fn compute_relay_vrf_modulo_assignments(
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
@@ -347,7 +338,10 @@ fn compute_relay_vrf_modulo_assignments(
|
||||
// has been executed.
|
||||
let cert = AssignmentCert {
|
||||
kind: AssignmentCertKind::RelayVRFModulo { sample: rvm_sample },
|
||||
vrf: (approval_types::VRFOutput(vrf_in_out.to_output()), approval_types::VRFProof(vrf_proof)),
|
||||
vrf: (
|
||||
approval_types::VRFOutput(vrf_in_out.to_output()),
|
||||
approval_types::VRFProof(vrf_proof),
|
||||
),
|
||||
};
|
||||
|
||||
// All assignments of type RelayVRFModulo have tranche 0.
|
||||
@@ -370,9 +364,8 @@ fn compute_relay_vrf_delay_assignments(
|
||||
assignments: &mut HashMap<CoreIndex, OurAssignment>,
|
||||
) {
|
||||
for (candidate_hash, core) in leaving_cores {
|
||||
let (vrf_in_out, vrf_proof, _) = assignments_key.vrf_sign(
|
||||
relay_vrf_delay_transcript(relay_vrf_story.clone(), core),
|
||||
);
|
||||
let (vrf_in_out, vrf_proof, _) =
|
||||
assignments_key.vrf_sign(relay_vrf_delay_transcript(relay_vrf_story.clone(), core));
|
||||
|
||||
let tranche = relay_vrf_delay_tranche(
|
||||
&vrf_in_out,
|
||||
@@ -382,24 +375,26 @@ fn compute_relay_vrf_delay_assignments(
|
||||
|
||||
let cert = AssignmentCert {
|
||||
kind: AssignmentCertKind::RelayVRFDelay { core_index: core },
|
||||
vrf: (approval_types::VRFOutput(vrf_in_out.to_output()), approval_types::VRFProof(vrf_proof)),
|
||||
vrf: (
|
||||
approval_types::VRFOutput(vrf_in_out.to_output()),
|
||||
approval_types::VRFProof(vrf_proof),
|
||||
),
|
||||
};
|
||||
|
||||
let our_assignment = OurAssignment {
|
||||
cert,
|
||||
tranche,
|
||||
validator_index,
|
||||
triggered: false,
|
||||
};
|
||||
let our_assignment = OurAssignment { cert, tranche, validator_index, triggered: false };
|
||||
|
||||
let used = match assignments.entry(core) {
|
||||
Entry::Vacant(e) => { let _ = e.insert(our_assignment); true }
|
||||
Entry::Occupied(mut e) => if e.get().tranche > our_assignment.tranche {
|
||||
e.insert(our_assignment);
|
||||
Entry::Vacant(e) => {
|
||||
let _ = e.insert(our_assignment);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
},
|
||||
Entry::Occupied(mut e) =>
|
||||
if e.get().tranche > our_assignment.tranche {
|
||||
e.insert(our_assignment);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
},
|
||||
};
|
||||
|
||||
if used {
|
||||
@@ -425,7 +420,7 @@ impl std::fmt::Display for InvalidAssignment {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for InvalidAssignment { }
|
||||
impl std::error::Error for InvalidAssignment {}
|
||||
|
||||
/// Checks the crypto of an assignment cert. Failure conditions:
|
||||
/// * Validator index out of bounds
|
||||
@@ -446,7 +441,8 @@ pub(crate) fn check_assignment_cert(
|
||||
assignment: &AssignmentCert,
|
||||
backing_group: GroupIndex,
|
||||
) -> Result<DelayTranche, InvalidAssignment> {
|
||||
let validator_public = config.assignment_keys
|
||||
let validator_public = config
|
||||
.assignment_keys
|
||||
.get(validator_index.0 as usize)
|
||||
.ok_or(InvalidAssignment)?;
|
||||
|
||||
@@ -454,34 +450,33 @@ pub(crate) fn check_assignment_cert(
|
||||
.map_err(|_| InvalidAssignment)?;
|
||||
|
||||
if claimed_core_index.0 >= config.n_cores {
|
||||
return Err(InvalidAssignment);
|
||||
return Err(InvalidAssignment)
|
||||
}
|
||||
|
||||
// Check that the validator was not part of the backing group
|
||||
// and not already assigned.
|
||||
let is_in_backing = is_in_backing_group(
|
||||
&config.validator_groups,
|
||||
validator_index,
|
||||
backing_group,
|
||||
);
|
||||
let is_in_backing =
|
||||
is_in_backing_group(&config.validator_groups, validator_index, backing_group);
|
||||
|
||||
if is_in_backing {
|
||||
return Err(InvalidAssignment);
|
||||
return Err(InvalidAssignment)
|
||||
}
|
||||
|
||||
let &(ref vrf_output, ref vrf_proof) = &assignment.vrf;
|
||||
match assignment.kind {
|
||||
AssignmentCertKind::RelayVRFModulo { sample } => {
|
||||
if sample >= config.relay_vrf_modulo_samples {
|
||||
return Err(InvalidAssignment);
|
||||
return Err(InvalidAssignment)
|
||||
}
|
||||
|
||||
let (vrf_in_out, _) = public.vrf_verify_extra(
|
||||
relay_vrf_modulo_transcript(relay_vrf_story, sample),
|
||||
&vrf_output.0,
|
||||
&vrf_proof.0,
|
||||
assigned_core_transcript(claimed_core_index),
|
||||
).map_err(|_| InvalidAssignment)?;
|
||||
let (vrf_in_out, _) = public
|
||||
.vrf_verify_extra(
|
||||
relay_vrf_modulo_transcript(relay_vrf_story, sample),
|
||||
&vrf_output.0,
|
||||
&vrf_proof.0,
|
||||
assigned_core_transcript(claimed_core_index),
|
||||
)
|
||||
.map_err(|_| InvalidAssignment)?;
|
||||
|
||||
// ensure that the `vrf_in_out` actually gives us the claimed core.
|
||||
if relay_vrf_modulo_core(&vrf_in_out, config.n_cores) == claimed_core_index {
|
||||
@@ -489,24 +484,26 @@ pub(crate) fn check_assignment_cert(
|
||||
} else {
|
||||
Err(InvalidAssignment)
|
||||
}
|
||||
}
|
||||
},
|
||||
AssignmentCertKind::RelayVRFDelay { core_index } => {
|
||||
if core_index != claimed_core_index {
|
||||
return Err(InvalidAssignment);
|
||||
return Err(InvalidAssignment)
|
||||
}
|
||||
|
||||
let (vrf_in_out, _) = public.vrf_verify(
|
||||
relay_vrf_delay_transcript(relay_vrf_story, core_index),
|
||||
&vrf_output.0,
|
||||
&vrf_proof.0,
|
||||
).map_err(|_| InvalidAssignment)?;
|
||||
let (vrf_in_out, _) = public
|
||||
.vrf_verify(
|
||||
relay_vrf_delay_transcript(relay_vrf_story, core_index),
|
||||
&vrf_output.0,
|
||||
&vrf_proof.0,
|
||||
)
|
||||
.map_err(|_| InvalidAssignment)?;
|
||||
|
||||
Ok(relay_vrf_delay_tranche(
|
||||
&vrf_in_out,
|
||||
config.n_delay_tranches,
|
||||
config.zeroth_delay_tranche_width,
|
||||
))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,22 +518,22 @@ fn is_in_backing_group(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use sp_keystore::CryptoStore;
|
||||
use sp_keyring::sr25519::Keyring as Sr25519Keyring;
|
||||
use polkadot_node_primitives::approval::{VRFOutput, VRFProof};
|
||||
use polkadot_primitives::v1::{Hash, ASSIGNMENT_KEY_TYPE_ID};
|
||||
use sp_application_crypto::sr25519;
|
||||
use sp_core::crypto::Pair as PairT;
|
||||
use polkadot_primitives::v1::{ASSIGNMENT_KEY_TYPE_ID, Hash};
|
||||
use polkadot_node_primitives::approval::{VRFOutput, VRFProof};
|
||||
use sp_keyring::sr25519::Keyring as Sr25519Keyring;
|
||||
use sp_keystore::CryptoStore;
|
||||
|
||||
// sets up a keystore with the given keyring accounts.
|
||||
async fn make_keystore(accounts: &[Sr25519Keyring]) -> LocalKeystore {
|
||||
let store = LocalKeystore::in_memory();
|
||||
|
||||
for s in accounts.iter().copied().map(|k| k.to_seed()) {
|
||||
store.sr25519_generate_new(
|
||||
ASSIGNMENT_KEY_TYPE_ID,
|
||||
Some(s.as_str()),
|
||||
).await.unwrap();
|
||||
store
|
||||
.sr25519_generate_new(ASSIGNMENT_KEY_TYPE_ID, Some(s.as_str()))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
store
|
||||
@@ -546,12 +543,15 @@ mod tests {
|
||||
assignment_keys_plus_random(accounts, 0)
|
||||
}
|
||||
|
||||
fn assignment_keys_plus_random(accounts: &[Sr25519Keyring], random: usize) -> Vec<AssignmentId> {
|
||||
let gen_random = (0..random).map(|_|
|
||||
AssignmentId::from(sr25519::Pair::generate().0.public())
|
||||
);
|
||||
fn assignment_keys_plus_random(
|
||||
accounts: &[Sr25519Keyring],
|
||||
random: usize,
|
||||
) -> Vec<AssignmentId> {
|
||||
let gen_random =
|
||||
(0..random).map(|_| AssignmentId::from(sr25519::Pair::generate().0.public()));
|
||||
|
||||
accounts.iter()
|
||||
accounts
|
||||
.iter()
|
||||
.map(|k| AssignmentId::from(k.public()))
|
||||
.chain(gen_random)
|
||||
.collect()
|
||||
@@ -562,12 +562,14 @@ mod tests {
|
||||
let big_groups = n_validators % n_groups;
|
||||
let scraps = n_groups * size;
|
||||
|
||||
(0..n_groups).map(|i| {
|
||||
(i * size .. (i + 1) *size)
|
||||
.chain(if i < big_groups { Some(scraps + i) } else { None })
|
||||
.map(|j| ValidatorIndex(j as _))
|
||||
.collect::<Vec<_>>()
|
||||
}).collect()
|
||||
(0..n_groups)
|
||||
.map(|i| {
|
||||
(i * size..(i + 1) * size)
|
||||
.chain(if i < big_groups { Some(scraps + i) } else { None })
|
||||
.map(|j| ValidatorIndex(j as _))
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
// used for generating assignments where the validity of the VRF doesn't matter.
|
||||
@@ -581,9 +583,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn assignments_produced_for_non_backing() {
|
||||
let keystore = futures::executor::block_on(
|
||||
make_keystore(&[Sr25519Keyring::Alice])
|
||||
);
|
||||
let keystore = futures::executor::block_on(make_keystore(&[Sr25519Keyring::Alice]));
|
||||
|
||||
let c_a = CandidateHash(Hash::repeat_byte(0));
|
||||
let c_b = CandidateHash(Hash::repeat_byte(1));
|
||||
@@ -598,7 +598,10 @@ mod tests {
|
||||
Sr25519Keyring::Bob,
|
||||
Sr25519Keyring::Charlie,
|
||||
]),
|
||||
validator_groups: vec![vec![ValidatorIndex(0)], vec![ValidatorIndex(1), ValidatorIndex(2)]],
|
||||
validator_groups: vec![
|
||||
vec![ValidatorIndex(0)],
|
||||
vec![ValidatorIndex(1), ValidatorIndex(2)],
|
||||
],
|
||||
n_cores: 2,
|
||||
zeroth_delay_tranche_width: 10,
|
||||
relay_vrf_modulo_samples: 3,
|
||||
@@ -615,9 +618,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn assign_to_nonzero_core() {
|
||||
let keystore = futures::executor::block_on(
|
||||
make_keystore(&[Sr25519Keyring::Alice])
|
||||
);
|
||||
let keystore = futures::executor::block_on(make_keystore(&[Sr25519Keyring::Alice]));
|
||||
|
||||
let c_a = CandidateHash(Hash::repeat_byte(0));
|
||||
let c_b = CandidateHash(Hash::repeat_byte(1));
|
||||
@@ -632,7 +633,10 @@ mod tests {
|
||||
Sr25519Keyring::Bob,
|
||||
Sr25519Keyring::Charlie,
|
||||
]),
|
||||
validator_groups: vec![vec![ValidatorIndex(0)], vec![ValidatorIndex(1), ValidatorIndex(2)]],
|
||||
validator_groups: vec![
|
||||
vec![ValidatorIndex(0)],
|
||||
vec![ValidatorIndex(1), ValidatorIndex(2)],
|
||||
],
|
||||
n_cores: 2,
|
||||
zeroth_delay_tranche_width: 10,
|
||||
relay_vrf_modulo_samples: 3,
|
||||
@@ -647,9 +651,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn succeeds_empty_for_0_cores() {
|
||||
let keystore = futures::executor::block_on(
|
||||
make_keystore(&[Sr25519Keyring::Alice])
|
||||
);
|
||||
let keystore = futures::executor::block_on(make_keystore(&[Sr25519Keyring::Alice]));
|
||||
|
||||
let relay_vrf_story = RelayVRFStory([42u8; 32]);
|
||||
let assignments = compute_assignments(
|
||||
@@ -689,14 +691,15 @@ mod tests {
|
||||
rotation_offset: usize,
|
||||
f: impl Fn(&mut MutatedAssignment) -> Option<bool>, // None = skip
|
||||
) {
|
||||
let keystore = futures::executor::block_on(
|
||||
make_keystore(&[Sr25519Keyring::Alice])
|
||||
);
|
||||
let keystore = futures::executor::block_on(make_keystore(&[Sr25519Keyring::Alice]));
|
||||
|
||||
let group_for_core = |i| GroupIndex(((i + rotation_offset) % n_cores) as _);
|
||||
|
||||
let config = Config {
|
||||
assignment_keys: assignment_keys_plus_random(&[Sr25519Keyring::Alice], n_validators - 1),
|
||||
assignment_keys: assignment_keys_plus_random(
|
||||
&[Sr25519Keyring::Alice],
|
||||
n_validators - 1,
|
||||
),
|
||||
validator_groups: basic_groups(n_validators, n_cores),
|
||||
n_cores: n_cores as u32,
|
||||
zeroth_delay_tranche_width: 10,
|
||||
@@ -710,11 +713,13 @@ mod tests {
|
||||
relay_vrf_story.clone(),
|
||||
&config,
|
||||
(0..n_cores)
|
||||
.map(|i| (
|
||||
CandidateHash(Hash::repeat_byte(i as u8)),
|
||||
CoreIndex(i as u32),
|
||||
group_for_core(i),
|
||||
))
|
||||
.map(|i| {
|
||||
(
|
||||
CandidateHash(Hash::repeat_byte(i as u8)),
|
||||
CoreIndex(i as u32),
|
||||
group_for_core(i),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
@@ -743,7 +748,8 @@ mod tests {
|
||||
relay_vrf_story.clone(),
|
||||
&mutated.cert,
|
||||
mutated.group,
|
||||
).is_ok();
|
||||
)
|
||||
.is_ok();
|
||||
|
||||
assert_eq!(expected, is_good)
|
||||
}
|
||||
@@ -787,7 +793,7 @@ mod tests {
|
||||
AssignmentCertKind::RelayVRFDelay { .. } => {
|
||||
m.cert.vrf = garbage_vrf();
|
||||
Some(false)
|
||||
}
|
||||
},
|
||||
_ => None, // skip everything else.
|
||||
}
|
||||
});
|
||||
@@ -800,7 +806,7 @@ mod tests {
|
||||
AssignmentCertKind::RelayVRFModulo { .. } => {
|
||||
m.cert.vrf = garbage_vrf();
|
||||
Some(false)
|
||||
}
|
||||
},
|
||||
_ => None, // skip everything else.
|
||||
}
|
||||
});
|
||||
@@ -813,7 +819,7 @@ mod tests {
|
||||
AssignmentCertKind::RelayVRFModulo { sample } => {
|
||||
m.config.relay_vrf_modulo_samples = sample;
|
||||
Some(false)
|
||||
}
|
||||
},
|
||||
_ => None, // skip everything else.
|
||||
}
|
||||
});
|
||||
@@ -826,7 +832,7 @@ mod tests {
|
||||
AssignmentCertKind::RelayVRFDelay { .. } => {
|
||||
m.core = CoreIndex((m.core.0 + 1) % 100);
|
||||
Some(false)
|
||||
}
|
||||
},
|
||||
_ => None, // skip everything else.
|
||||
}
|
||||
});
|
||||
@@ -839,7 +845,7 @@ mod tests {
|
||||
AssignmentCertKind::RelayVRFModulo { .. } => {
|
||||
m.core = CoreIndex((m.core.0 + 1) % 100);
|
||||
Some(false)
|
||||
}
|
||||
},
|
||||
_ => None, // skip everything else.
|
||||
}
|
||||
});
|
||||
|
||||
@@ -28,43 +28,42 @@
|
||||
//!
|
||||
//! We maintain a rolling window of session indices. This starts as empty
|
||||
|
||||
use polkadot_node_subsystem::{
|
||||
overseer,
|
||||
messages::{
|
||||
RuntimeApiMessage, RuntimeApiRequest, ChainApiMessage, ApprovalDistributionMessage,
|
||||
ChainSelectionMessage,
|
||||
},
|
||||
SubsystemContext, SubsystemError, SubsystemResult,
|
||||
};
|
||||
use polkadot_node_subsystem_util::determine_new_blocks;
|
||||
use polkadot_node_subsystem_util::rolling_session_window::{
|
||||
RollingSessionWindow, SessionWindowUpdate,
|
||||
};
|
||||
use polkadot_primitives::v1::{
|
||||
Hash, SessionIndex, CandidateEvent, Header, CandidateHash,
|
||||
CandidateReceipt, CoreIndex, GroupIndex, BlockNumber, ConsensusLog,
|
||||
};
|
||||
use polkadot_node_jaeger as jaeger;
|
||||
use polkadot_node_primitives::approval::{
|
||||
self as approval_types, BlockApprovalMeta, RelayVRFStory,
|
||||
};
|
||||
use polkadot_node_jaeger as jaeger;
|
||||
use polkadot_node_subsystem::{
|
||||
messages::{
|
||||
ApprovalDistributionMessage, ChainApiMessage, ChainSelectionMessage, RuntimeApiMessage,
|
||||
RuntimeApiRequest,
|
||||
},
|
||||
overseer, SubsystemContext, SubsystemError, SubsystemResult,
|
||||
};
|
||||
use polkadot_node_subsystem_util::{
|
||||
determine_new_blocks,
|
||||
rolling_session_window::{RollingSessionWindow, SessionWindowUpdate},
|
||||
};
|
||||
use polkadot_primitives::v1::{
|
||||
BlockNumber, CandidateEvent, CandidateHash, CandidateReceipt, ConsensusLog, CoreIndex,
|
||||
GroupIndex, Hash, Header, SessionIndex,
|
||||
};
|
||||
use sc_keystore::LocalKeystore;
|
||||
use sp_consensus_slots::Slot;
|
||||
|
||||
use futures::prelude::*;
|
||||
use futures::channel::oneshot;
|
||||
use bitvec::order::Lsb0 as BitOrderLsb0;
|
||||
use futures::{channel::oneshot, prelude::*};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::{collections::HashMap, convert::TryFrom};
|
||||
|
||||
use super::approval_db::v1;
|
||||
use crate::backend::{Backend, OverlayedBackend};
|
||||
use crate::persisted_entries::CandidateEntry;
|
||||
use crate::criteria::{AssignmentCriteria, OurAssignment};
|
||||
use crate::time::{slot_number_to_tick, Tick};
|
||||
use crate::{
|
||||
backend::{Backend, OverlayedBackend},
|
||||
criteria::{AssignmentCriteria, OurAssignment},
|
||||
persisted_entries::CandidateEntry,
|
||||
time::{slot_number_to_tick, Tick},
|
||||
};
|
||||
|
||||
use super::{LOG_TARGET, State};
|
||||
use super::{State, LOG_TARGET};
|
||||
|
||||
struct ImportedBlockInfo {
|
||||
included_candidates: Vec<(CandidateHash, CandidateReceipt, CoreIndex, GroupIndex)>,
|
||||
@@ -99,7 +98,8 @@ async fn imported_block_info(
|
||||
ctx.send_message(RuntimeApiMessage::Request(
|
||||
block_hash,
|
||||
RuntimeApiRequest::CandidateEvents(c_tx),
|
||||
)).await;
|
||||
))
|
||||
.await;
|
||||
|
||||
let events: Vec<CandidateEvent> = match c_rx.await {
|
||||
Ok(Ok(events)) => events,
|
||||
@@ -107,11 +107,14 @@ async fn imported_block_info(
|
||||
Err(_) => return Ok(None),
|
||||
};
|
||||
|
||||
events.into_iter().filter_map(|e| match e {
|
||||
CandidateEvent::CandidateIncluded(receipt, _, core, group)
|
||||
=> Some((receipt.hash(), receipt, core, group)),
|
||||
_ => None,
|
||||
}).collect()
|
||||
events
|
||||
.into_iter()
|
||||
.filter_map(|e| match e {
|
||||
CandidateEvent::CandidateIncluded(receipt, _, core, group) =>
|
||||
Some((receipt.hash(), receipt, core, group)),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
// fetch session. ignore blocks that are too old, but unless sessions are really
|
||||
@@ -121,7 +124,8 @@ async fn imported_block_info(
|
||||
ctx.send_message(RuntimeApiMessage::Request(
|
||||
block_header.parent_hash,
|
||||
RuntimeApiRequest::SessionIndexForChild(s_tx),
|
||||
)).await;
|
||||
))
|
||||
.await;
|
||||
|
||||
let session_index = match s_rx.await {
|
||||
Ok(Ok(s)) => s,
|
||||
@@ -130,10 +134,14 @@ async fn imported_block_info(
|
||||
};
|
||||
|
||||
if env.session_window.earliest_session().map_or(true, |e| session_index < e) {
|
||||
tracing::debug!(target: LOG_TARGET, "Block {} is from ancient session {}. Skipping",
|
||||
block_hash, session_index);
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Block {} is from ancient session {}. Skipping",
|
||||
block_hash,
|
||||
session_index
|
||||
);
|
||||
|
||||
return Ok(None);
|
||||
return Ok(None)
|
||||
}
|
||||
|
||||
session_index
|
||||
@@ -162,7 +170,8 @@ async fn imported_block_info(
|
||||
ctx.send_message(RuntimeApiMessage::Request(
|
||||
block_hash,
|
||||
RuntimeApiRequest::CurrentBabeEpoch(s_tx),
|
||||
)).await;
|
||||
))
|
||||
.await;
|
||||
|
||||
match s_rx.await {
|
||||
Ok(Ok(s)) => s,
|
||||
@@ -180,8 +189,8 @@ async fn imported_block_info(
|
||||
block_hash,
|
||||
);
|
||||
|
||||
return Ok(None);
|
||||
}
|
||||
return Ok(None)
|
||||
},
|
||||
};
|
||||
|
||||
let (assignments, slot, relay_vrf_story) = {
|
||||
@@ -201,7 +210,8 @@ async fn imported_block_info(
|
||||
&env.keystore,
|
||||
relay_vrf.clone(),
|
||||
&crate::criteria::Config::from(session_info),
|
||||
included_candidates.iter()
|
||||
included_candidates
|
||||
.iter()
|
||||
.map(|(c_hash, _, core, group)| (*c_hash, *core, *group))
|
||||
.collect(),
|
||||
);
|
||||
@@ -210,7 +220,7 @@ async fn imported_block_info(
|
||||
},
|
||||
Err(_) => return Ok(None),
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
@@ -218,16 +228,12 @@ async fn imported_block_info(
|
||||
block_hash,
|
||||
);
|
||||
|
||||
return Ok(None);
|
||||
}
|
||||
return Ok(None)
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
n_assignments = assignments.len(),
|
||||
"Produced assignments"
|
||||
);
|
||||
tracing::trace!(target: LOG_TARGET, n_assignments = assignments.len(), "Produced assignments");
|
||||
|
||||
let force_approve =
|
||||
block_header.digest.convert_first(|l| match ConsensusLog::from_digest_item(l) {
|
||||
@@ -241,7 +247,7 @@ async fn imported_block_info(
|
||||
);
|
||||
|
||||
Some(num)
|
||||
}
|
||||
},
|
||||
Ok(Some(_)) => None,
|
||||
Ok(None) => None,
|
||||
Err(err) => {
|
||||
@@ -253,7 +259,7 @@ async fn imported_block_info(
|
||||
);
|
||||
|
||||
None
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Ok(Some(ImportedBlockInfo {
|
||||
@@ -291,8 +297,7 @@ pub(crate) async fn handle_new_head(
|
||||
db: &mut OverlayedBackend<'_, impl Backend>,
|
||||
head: Hash,
|
||||
finalized_number: &Option<BlockNumber>,
|
||||
) -> SubsystemResult<Vec<BlockImportedCandidates>>
|
||||
{
|
||||
) -> SubsystemResult<Vec<BlockImportedCandidates>> {
|
||||
// Update session info based on most recent head.
|
||||
|
||||
let mut span = jaeger::Span::new(head, "approval-checking-import");
|
||||
@@ -309,13 +314,13 @@ pub(crate) async fn handle_new_head(
|
||||
e,
|
||||
);
|
||||
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
return Ok(Vec::new())
|
||||
},
|
||||
Ok(None) => {
|
||||
tracing::warn!(target: LOG_TARGET, "Missing header for new head {}", head);
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
Ok(Some(h)) => h
|
||||
return Ok(Vec::new())
|
||||
},
|
||||
Ok(Some(h)) => h,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -329,15 +334,15 @@ pub(crate) async fn handle_new_head(
|
||||
);
|
||||
|
||||
return Ok(Vec::new())
|
||||
}
|
||||
},
|
||||
Ok(a @ SessionWindowUpdate::Advanced { .. }) => {
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
update = ?a,
|
||||
"Advanced session window for approvals",
|
||||
);
|
||||
}
|
||||
Ok(_) => {}
|
||||
},
|
||||
Ok(_) => {},
|
||||
}
|
||||
|
||||
// If we've just started the node and haven't yet received any finality notifications,
|
||||
@@ -351,12 +356,14 @@ pub(crate) async fn handle_new_head(
|
||||
&header,
|
||||
lower_bound_number,
|
||||
)
|
||||
.map_err(|e| SubsystemError::with_origin("approval-voting", e))
|
||||
.await?;
|
||||
.map_err(|e| SubsystemError::with_origin("approval-voting", e))
|
||||
.await?;
|
||||
|
||||
span.add_uint_tag("new-blocks", new_blocks.len() as u64);
|
||||
|
||||
if new_blocks.is_empty() { return Ok(Vec::new()) }
|
||||
if new_blocks.is_empty() {
|
||||
return Ok(Vec::new())
|
||||
}
|
||||
|
||||
let mut approval_meta: Vec<BlockApprovalMeta> = Vec::with_capacity(new_blocks.len());
|
||||
let mut imported_candidates = Vec::with_capacity(new_blocks.len());
|
||||
@@ -376,9 +383,11 @@ pub(crate) async fn handle_new_head(
|
||||
None => {
|
||||
// It's possible that we've lost a race with finality.
|
||||
let (tx, rx) = oneshot::channel();
|
||||
ctx.send_message(
|
||||
ChainApiMessage::FinalizedBlockHash(block_header.number.clone(), tx)
|
||||
).await;
|
||||
ctx.send_message(ChainApiMessage::FinalizedBlockHash(
|
||||
block_header.number.clone(),
|
||||
tx,
|
||||
))
|
||||
.await;
|
||||
|
||||
let lost_to_finality = match rx.await {
|
||||
Ok(Ok(Some(h))) if h != block_hash => true,
|
||||
@@ -395,7 +404,7 @@ pub(crate) async fn handle_new_head(
|
||||
);
|
||||
}
|
||||
|
||||
return Ok(Vec::new());
|
||||
return Ok(Vec::new())
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -420,7 +429,9 @@ pub(crate) async fn handle_new_head(
|
||||
force_approve,
|
||||
} = imported_block_info;
|
||||
|
||||
let session_info = state.session_window.session_info(session_index)
|
||||
let session_info = state
|
||||
.session_window
|
||||
.session_info(session_index)
|
||||
.expect("imported_block_info requires session to be available; qed");
|
||||
|
||||
let (block_tick, no_show_duration) = {
|
||||
@@ -432,7 +443,8 @@ pub(crate) async fn handle_new_head(
|
||||
(block_tick, no_show_duration)
|
||||
};
|
||||
let needed_approvals = session_info.needed_approvals;
|
||||
let validator_group_lens: Vec<usize> = session_info.validator_groups.iter().map(|v| v.len()).collect();
|
||||
let validator_group_lens: Vec<usize> =
|
||||
session_info.validator_groups.iter().map(|v| v.len()).collect();
|
||||
// insta-approve candidates on low-node testnets:
|
||||
// cf. https://github.com/paritytech/polkadot/issues/2411
|
||||
let num_candidates = included_candidates.len();
|
||||
@@ -447,10 +459,10 @@ pub(crate) async fn handle_new_head(
|
||||
} else {
|
||||
let mut result = bitvec::bitvec![BitOrderLsb0, u8; 0; num_candidates];
|
||||
for (i, &(_, _, _, backing_group)) in included_candidates.iter().enumerate() {
|
||||
let backing_group_size = validator_group_lens.get(backing_group.0 as usize)
|
||||
.copied()
|
||||
.unwrap_or(0);
|
||||
let needed_approvals = usize::try_from(needed_approvals).expect("usize is at least u32; qed");
|
||||
let backing_group_size =
|
||||
validator_group_lens.get(backing_group.0 as usize).copied().unwrap_or(0);
|
||||
let needed_approvals =
|
||||
usize::try_from(needed_approvals).expect("usize is at least u32; qed");
|
||||
if n_validators.saturating_sub(backing_group_size) < needed_approvals {
|
||||
result.set(i, true);
|
||||
}
|
||||
@@ -481,19 +493,16 @@ pub(crate) async fn handle_new_head(
|
||||
session: session_index,
|
||||
slot,
|
||||
relay_vrf_story: relay_vrf_story.0,
|
||||
candidates: included_candidates.iter()
|
||||
.map(|(hash, _, core, _)| (*core, *hash)).collect(),
|
||||
candidates: included_candidates
|
||||
.iter()
|
||||
.map(|(hash, _, core, _)| (*core, *hash))
|
||||
.collect(),
|
||||
approved_bitfield,
|
||||
children: Vec::new(),
|
||||
};
|
||||
|
||||
if let Some(up_to) = force_approve {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
?block_hash,
|
||||
up_to,
|
||||
"Enacting force-approve",
|
||||
);
|
||||
tracing::debug!(target: LOG_TARGET, ?block_hash, up_to, "Enacting force-approve",);
|
||||
|
||||
let approved_hashes = crate::ops::force_approve(db, block_hash, up_to)
|
||||
.map_err(|e| SubsystemError::with_origin("approval-voting", e))?;
|
||||
@@ -511,19 +520,19 @@ pub(crate) async fn handle_new_head(
|
||||
"Writing BlockEntry",
|
||||
);
|
||||
|
||||
let candidate_entries = crate::ops::add_block_entry(
|
||||
db,
|
||||
block_entry.into(),
|
||||
n_validators,
|
||||
|candidate_hash| {
|
||||
included_candidates.iter().find(|(hash, _, _, _)| candidate_hash == hash)
|
||||
.map(|(_, receipt, core, backing_group)| super::ops::NewCandidateInfo::new(
|
||||
receipt.clone(),
|
||||
*backing_group,
|
||||
assignments.get(core).map(|a| a.clone().into()),
|
||||
))
|
||||
}
|
||||
).map_err(|e| SubsystemError::with_origin("approval-voting", e))?;
|
||||
let candidate_entries =
|
||||
crate::ops::add_block_entry(db, block_entry.into(), n_validators, |candidate_hash| {
|
||||
included_candidates.iter().find(|(hash, _, _, _)| candidate_hash == hash).map(
|
||||
|(_, receipt, core, backing_group)| {
|
||||
super::ops::NewCandidateInfo::new(
|
||||
receipt.clone(),
|
||||
*backing_group,
|
||||
assignments.get(core).map(|a| a.clone().into()),
|
||||
)
|
||||
},
|
||||
)
|
||||
})
|
||||
.map_err(|e| SubsystemError::with_origin("approval-voting", e))?;
|
||||
approval_meta.push(BlockApprovalMeta {
|
||||
hash: block_hash,
|
||||
number: block_header.number,
|
||||
@@ -532,18 +541,16 @@ pub(crate) async fn handle_new_head(
|
||||
slot,
|
||||
});
|
||||
|
||||
imported_candidates.push(
|
||||
BlockImportedCandidates {
|
||||
block_hash,
|
||||
block_number: block_header.number,
|
||||
block_tick,
|
||||
no_show_duration,
|
||||
imported_candidates: candidate_entries
|
||||
.into_iter()
|
||||
.map(|(h, e)| (h, e.into()))
|
||||
.collect(),
|
||||
}
|
||||
);
|
||||
imported_candidates.push(BlockImportedCandidates {
|
||||
block_hash,
|
||||
block_number: block_header.number,
|
||||
block_tick,
|
||||
no_show_duration,
|
||||
imported_candidates: candidate_entries
|
||||
.into_iter()
|
||||
.map(|(h, e)| (h, e.into()))
|
||||
.collect(),
|
||||
});
|
||||
}
|
||||
|
||||
tracing::trace!(
|
||||
@@ -562,33 +569,30 @@ pub(crate) async fn handle_new_head(
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::approval_db::v1::DbBackend;
|
||||
use polkadot_node_subsystem_test_helpers::make_subsystem_context;
|
||||
use polkadot_node_primitives::approval::{VRFOutput, VRFProof};
|
||||
use polkadot_primitives::v1::{SessionInfo, ValidatorIndex};
|
||||
use polkadot_node_subsystem::messages::AllMessages;
|
||||
use sp_core::testing::TaskExecutor;
|
||||
pub(crate) use sp_runtime::{Digest, DigestItem};
|
||||
pub(crate) use sp_consensus_babe::{
|
||||
Epoch as BabeEpoch, BabeEpochConfiguration, AllowedSlots,
|
||||
};
|
||||
pub(crate) use sp_consensus_babe::digests::{CompatibleDigestItem, PreDigest, SecondaryVRFPreDigest};
|
||||
use sp_keyring::sr25519::Keyring as Sr25519Keyring;
|
||||
use assert_matches::assert_matches;
|
||||
use merlin::Transcript;
|
||||
use std::{pin::Pin, sync::Arc};
|
||||
use kvdb::KeyValueDB;
|
||||
use merlin::Transcript;
|
||||
use polkadot_node_primitives::approval::{VRFOutput, VRFProof};
|
||||
use polkadot_node_subsystem::messages::AllMessages;
|
||||
use polkadot_node_subsystem_test_helpers::make_subsystem_context;
|
||||
use polkadot_primitives::v1::{SessionInfo, ValidatorIndex};
|
||||
pub(crate) use sp_consensus_babe::{
|
||||
digests::{CompatibleDigestItem, PreDigest, SecondaryVRFPreDigest},
|
||||
AllowedSlots, BabeEpochConfiguration, Epoch as BabeEpoch,
|
||||
};
|
||||
use sp_core::testing::TaskExecutor;
|
||||
use sp_keyring::sr25519::Keyring as Sr25519Keyring;
|
||||
pub(crate) use sp_runtime::{Digest, DigestItem};
|
||||
use std::{pin::Pin, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
APPROVAL_SESSIONS, criteria, BlockEntry,
|
||||
approval_db::v1::Config as DatabaseConfig,
|
||||
approval_db::v1::Config as DatabaseConfig, criteria, BlockEntry, APPROVAL_SESSIONS,
|
||||
};
|
||||
|
||||
const DATA_COL: u32 = 0;
|
||||
const NUM_COLUMNS: u32 = 1;
|
||||
|
||||
const TEST_CONFIG: DatabaseConfig = DatabaseConfig {
|
||||
col_data: DATA_COL,
|
||||
};
|
||||
const TEST_CONFIG: DatabaseConfig = DatabaseConfig { col_data: DATA_COL };
|
||||
#[derive(Default)]
|
||||
struct MockClock;
|
||||
|
||||
@@ -598,9 +602,7 @@ pub(crate) mod tests {
|
||||
}
|
||||
|
||||
fn wait(&self, _tick: Tick) -> Pin<Box<dyn Future<Output = ()> + Send + 'static>> {
|
||||
Box::pin(async move {
|
||||
()
|
||||
})
|
||||
Box::pin(async move { () })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -633,7 +635,11 @@ pub(crate) mod tests {
|
||||
_keystore: &LocalKeystore,
|
||||
_relay_vrf_story: polkadot_node_primitives::approval::RelayVRFStory,
|
||||
_config: &criteria::Config,
|
||||
_leaving_cores: Vec<(CandidateHash, polkadot_primitives::v1::CoreIndex, polkadot_primitives::v1::GroupIndex)>,
|
||||
_leaving_cores: Vec<(
|
||||
CandidateHash,
|
||||
polkadot_primitives::v1::CoreIndex,
|
||||
polkadot_primitives::v1::GroupIndex,
|
||||
)>,
|
||||
) -> HashMap<polkadot_primitives::v1::CoreIndex, criteria::OurAssignment> {
|
||||
HashMap::new()
|
||||
}
|
||||
@@ -675,7 +681,6 @@ pub(crate) mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn imported_block_info_is_good() {
|
||||
let pool = TaskExecutor::new();
|
||||
@@ -691,12 +696,7 @@ pub(crate) mod tests {
|
||||
let mut d = Digest::default();
|
||||
let (vrf_output, vrf_proof) = garbage_vrf();
|
||||
d.push(DigestItem::babe_pre_digest(PreDigest::SecondaryVRF(
|
||||
SecondaryVRFPreDigest {
|
||||
authority_index: 0,
|
||||
slot,
|
||||
vrf_output,
|
||||
vrf_proof,
|
||||
}
|
||||
SecondaryVRFPreDigest { authority_index: 0, slot, vrf_output, vrf_proof },
|
||||
)));
|
||||
|
||||
d
|
||||
@@ -719,13 +719,15 @@ pub(crate) mod tests {
|
||||
(make_candidate(2.into()), CoreIndex(1), GroupIndex(3)),
|
||||
];
|
||||
|
||||
|
||||
let inclusion_events = candidates.iter().cloned()
|
||||
let inclusion_events = candidates
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|(r, c, g)| CandidateEvent::CandidateIncluded(r, Vec::new().into(), c, g))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let test_fut = {
|
||||
let included_candidates = candidates.iter()
|
||||
let included_candidates = candidates
|
||||
.iter()
|
||||
.map(|(r, c, g)| (r.hash(), r.clone(), *c, *g))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -743,12 +745,8 @@ pub(crate) mod tests {
|
||||
keystore: &LocalKeystore::in_memory(),
|
||||
};
|
||||
|
||||
let info = imported_block_info(
|
||||
&mut ctx,
|
||||
env,
|
||||
hash,
|
||||
&header,
|
||||
).await.unwrap().unwrap();
|
||||
let info =
|
||||
imported_block_info(&mut ctx, env, hash, &header).await.unwrap().unwrap();
|
||||
|
||||
assert_eq!(info.included_candidates, included_candidates);
|
||||
assert_eq!(info.session_index, session);
|
||||
@@ -835,7 +833,9 @@ pub(crate) mod tests {
|
||||
(make_candidate(2.into()), CoreIndex(1), GroupIndex(3)),
|
||||
];
|
||||
|
||||
let inclusion_events = candidates.iter().cloned()
|
||||
let inclusion_events = candidates
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|(r, c, g)| CandidateEvent::CandidateIncluded(r, Vec::new().into(), c, g))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -854,12 +854,7 @@ pub(crate) mod tests {
|
||||
keystore: &LocalKeystore::in_memory(),
|
||||
};
|
||||
|
||||
let info = imported_block_info(
|
||||
&mut ctx,
|
||||
env,
|
||||
hash,
|
||||
&header,
|
||||
).await.unwrap();
|
||||
let info = imported_block_info(&mut ctx, env, hash, &header).await.unwrap();
|
||||
|
||||
assert!(info.is_none());
|
||||
})
|
||||
@@ -940,7 +935,9 @@ pub(crate) mod tests {
|
||||
(make_candidate(2.into()), CoreIndex(1), GroupIndex(3)),
|
||||
];
|
||||
|
||||
let inclusion_events = candidates.iter().cloned()
|
||||
let inclusion_events = candidates
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|(r, c, g)| CandidateEvent::CandidateIncluded(r, Vec::new().into(), c, g))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -955,12 +952,7 @@ pub(crate) mod tests {
|
||||
keystore: &LocalKeystore::in_memory(),
|
||||
};
|
||||
|
||||
let info = imported_block_info(
|
||||
&mut ctx,
|
||||
env,
|
||||
hash,
|
||||
&header,
|
||||
).await.unwrap();
|
||||
let info = imported_block_info(&mut ctx, env, hash, &header).await.unwrap();
|
||||
|
||||
assert!(info.is_none());
|
||||
})
|
||||
@@ -1008,12 +1000,7 @@ pub(crate) mod tests {
|
||||
let mut d = Digest::default();
|
||||
let (vrf_output, vrf_proof) = garbage_vrf();
|
||||
d.push(DigestItem::babe_pre_digest(PreDigest::SecondaryVRF(
|
||||
SecondaryVRFPreDigest {
|
||||
authority_index: 0,
|
||||
slot,
|
||||
vrf_output,
|
||||
vrf_proof,
|
||||
}
|
||||
SecondaryVRFPreDigest { authority_index: 0, slot, vrf_output, vrf_proof },
|
||||
)));
|
||||
|
||||
d.push(ConsensusLog::ForceApprove(3).into());
|
||||
@@ -1038,13 +1025,15 @@ pub(crate) mod tests {
|
||||
(make_candidate(2.into()), CoreIndex(1), GroupIndex(3)),
|
||||
];
|
||||
|
||||
|
||||
let inclusion_events = candidates.iter().cloned()
|
||||
let inclusion_events = candidates
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|(r, c, g)| CandidateEvent::CandidateIncluded(r, Vec::new().into(), c, g))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let test_fut = {
|
||||
let included_candidates = candidates.iter()
|
||||
let included_candidates = candidates
|
||||
.iter()
|
||||
.map(|(r, c, g)| (r.hash(), r.clone(), *c, *g))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -1062,12 +1051,8 @@ pub(crate) mod tests {
|
||||
keystore: &LocalKeystore::in_memory(),
|
||||
};
|
||||
|
||||
let info = imported_block_info(
|
||||
&mut ctx,
|
||||
env,
|
||||
hash,
|
||||
&header,
|
||||
).await.unwrap().unwrap();
|
||||
let info =
|
||||
imported_block_info(&mut ctx, env, hash, &header).await.unwrap().unwrap();
|
||||
|
||||
assert_eq!(info.included_candidates, included_candidates);
|
||||
assert_eq!(info.session_index, session);
|
||||
@@ -1159,12 +1144,7 @@ pub(crate) mod tests {
|
||||
let mut d = Digest::default();
|
||||
let (vrf_output, vrf_proof) = garbage_vrf();
|
||||
d.push(DigestItem::babe_pre_digest(PreDigest::SecondaryVRF(
|
||||
SecondaryVRFPreDigest {
|
||||
authority_index: 0,
|
||||
slot,
|
||||
vrf_output,
|
||||
vrf_proof,
|
||||
}
|
||||
SecondaryVRFPreDigest { authority_index: 0, slot, vrf_output, vrf_proof },
|
||||
)));
|
||||
|
||||
d
|
||||
@@ -1186,7 +1166,9 @@ pub(crate) mod tests {
|
||||
(make_candidate(1.into()), CoreIndex(0), GroupIndex(0)),
|
||||
(make_candidate(2.into()), CoreIndex(1), GroupIndex(1)),
|
||||
];
|
||||
let inclusion_events = candidates.iter().cloned()
|
||||
let inclusion_events = candidates
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|(r, c, g)| CandidateEvent::CandidateIncluded(r, Vec::new().into(), c, g))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -1202,7 +1184,8 @@ pub(crate) mod tests {
|
||||
candidates: Vec::new(),
|
||||
approved_bitfield: Default::default(),
|
||||
children: Vec::new(),
|
||||
}.into()
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
let write_ops = overlay_db.into_write_ops();
|
||||
@@ -1211,13 +1194,9 @@ pub(crate) mod tests {
|
||||
let test_fut = {
|
||||
Box::pin(async move {
|
||||
let mut overlay_db = OverlayedBackend::new(&db);
|
||||
let result = handle_new_head(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&mut overlay_db,
|
||||
hash,
|
||||
&Some(1),
|
||||
).await.unwrap();
|
||||
let result = handle_new_head(&mut ctx, &mut state, &mut overlay_db, hash, &Some(1))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let write_ops = overlay_db.into_write_ops();
|
||||
db.write(write_ops).unwrap();
|
||||
@@ -1229,14 +1208,11 @@ pub(crate) mod tests {
|
||||
assert_eq!(candidates[1].1.approvals().len(), 6);
|
||||
// the first candidate should be insta-approved
|
||||
// the second should not
|
||||
let entry: BlockEntry = v1::load_block_entry(
|
||||
db_writer.as_ref(),
|
||||
&TEST_CONFIG,
|
||||
&hash,
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.into();
|
||||
let entry: BlockEntry =
|
||||
v1::load_block_entry(db_writer.as_ref(), &TEST_CONFIG, &hash)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.into();
|
||||
assert!(entry.is_candidate_approved(&candidates[0].0));
|
||||
assert!(!entry.is_candidate_approved(&candidates[1].0));
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -19,21 +19,18 @@
|
||||
|
||||
use polkadot_node_subsystem::SubsystemResult;
|
||||
|
||||
use polkadot_primitives::v1::{
|
||||
CandidateHash, CandidateReceipt, BlockNumber, GroupIndex, Hash,
|
||||
use bitvec::order::Lsb0 as BitOrderLsb0;
|
||||
use polkadot_primitives::v1::{BlockNumber, CandidateHash, CandidateReceipt, GroupIndex, Hash};
|
||||
|
||||
use std::{
|
||||
collections::{hash_map::Entry, BTreeMap, HashMap},
|
||||
convert::Into,
|
||||
};
|
||||
use bitvec::{order::Lsb0 as BitOrderLsb0};
|
||||
|
||||
use std::convert::Into;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
use super::persisted_entries::{ApprovalEntry, CandidateEntry, BlockEntry};
|
||||
use super::backend::{Backend, OverlayedBackend};
|
||||
use super::approval_db::{
|
||||
v1::{
|
||||
OurAssignment, StoredBlockRange,
|
||||
},
|
||||
use super::{
|
||||
approval_db::v1::{OurAssignment, StoredBlockRange},
|
||||
backend::{Backend, OverlayedBackend},
|
||||
persisted_entries::{ApprovalEntry, BlockEntry, CandidateEntry},
|
||||
};
|
||||
|
||||
/// Information about a new candidate necessary to instantiate the requisite
|
||||
@@ -75,7 +72,7 @@ fn visit_and_remove_block_entry(
|
||||
None => continue, // Should not happen except for corrupt DB
|
||||
Some(c) => c,
|
||||
})
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
candidate.block_assignments.remove(&block_hash);
|
||||
@@ -111,11 +108,7 @@ pub fn canonicalize(
|
||||
overlay_db.delete_blocks_at_height(i);
|
||||
|
||||
for b in at_height {
|
||||
let _ = visit_and_remove_block_entry(
|
||||
b,
|
||||
overlay_db,
|
||||
&mut visited_candidates,
|
||||
)?;
|
||||
let _ = visit_and_remove_block_entry(b, overlay_db, &mut visited_candidates)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,11 +122,7 @@ pub fn canonicalize(
|
||||
let mut pruned_branches = Vec::new();
|
||||
|
||||
for b in at_height {
|
||||
let children = visit_and_remove_block_entry(
|
||||
b,
|
||||
overlay_db,
|
||||
&mut visited_candidates,
|
||||
)?;
|
||||
let children = visit_and_remove_block_entry(b, overlay_db, &mut visited_candidates)?;
|
||||
|
||||
if b != canon_hash {
|
||||
pruned_branches.extend(children);
|
||||
@@ -145,13 +134,11 @@ pub fn canonicalize(
|
||||
|
||||
// Follow all children of non-canonicalized blocks.
|
||||
{
|
||||
let mut frontier: Vec<(BlockNumber, Hash)> = pruned_branches.into_iter().map(|h| (canon_number + 1, h)).collect();
|
||||
let mut frontier: Vec<(BlockNumber, Hash)> =
|
||||
pruned_branches.into_iter().map(|h| (canon_number + 1, h)).collect();
|
||||
while let Some((height, next_child)) = frontier.pop() {
|
||||
let children = visit_and_remove_block_entry(
|
||||
next_child,
|
||||
overlay_db,
|
||||
&mut visited_candidates,
|
||||
)?;
|
||||
let children =
|
||||
visit_and_remove_block_entry(next_child, overlay_db, &mut visited_candidates)?;
|
||||
|
||||
// extend the frontier of branches to include the given height.
|
||||
frontier.extend(children.into_iter().map(|h| (height + 1, h)));
|
||||
@@ -188,10 +175,7 @@ pub fn canonicalize(
|
||||
// due to the fork pruning, this range actually might go too far above where our actual highest block is,
|
||||
// if a relatively short fork is canonicalized.
|
||||
// TODO https://github.com/paritytech/polkadot/issues/3389
|
||||
let new_range = StoredBlockRange(
|
||||
canon_number + 1,
|
||||
std::cmp::max(range.1, canon_number + 2),
|
||||
);
|
||||
let new_range = StoredBlockRange(canon_number + 1, std::cmp::max(range.1, canon_number + 2));
|
||||
|
||||
overlay_db.write_stored_block_range(new_range);
|
||||
|
||||
@@ -246,21 +230,20 @@ pub fn add_block_entry(
|
||||
// read and write all updated entries.
|
||||
{
|
||||
for &(_, ref candidate_hash) in entry.candidates() {
|
||||
let NewCandidateInfo {
|
||||
candidate,
|
||||
backing_group,
|
||||
our_assignment,
|
||||
} = match candidate_info(candidate_hash) {
|
||||
None => return Ok(Vec::new()),
|
||||
Some(info) => info,
|
||||
};
|
||||
let NewCandidateInfo { candidate, backing_group, our_assignment } =
|
||||
match candidate_info(candidate_hash) {
|
||||
None => return Ok(Vec::new()),
|
||||
Some(info) => info,
|
||||
};
|
||||
|
||||
let mut candidate_entry = store.load_candidate_entry(&candidate_hash)?
|
||||
.unwrap_or_else(move || CandidateEntry {
|
||||
candidate,
|
||||
session,
|
||||
block_assignments: BTreeMap::new(),
|
||||
approvals: bitvec::bitvec![BitOrderLsb0, u8; 0; n_validators],
|
||||
let mut candidate_entry =
|
||||
store.load_candidate_entry(&candidate_hash)?.unwrap_or_else(move || {
|
||||
CandidateEntry {
|
||||
candidate,
|
||||
session,
|
||||
block_assignments: BTreeMap::new(),
|
||||
approvals: bitvec::bitvec![BitOrderLsb0, u8; 0; n_validators],
|
||||
}
|
||||
});
|
||||
|
||||
candidate_entry.block_assignments.insert(
|
||||
@@ -272,7 +255,7 @@ pub fn add_block_entry(
|
||||
None,
|
||||
bitvec::bitvec![BitOrderLsb0, u8; 0; n_validators],
|
||||
false,
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
store.write_candidate_entry(candidate_entry.clone());
|
||||
@@ -313,7 +296,6 @@ pub fn force_approve(
|
||||
// iterate back to the `up_to` block, and then iterate backwards until all blocks
|
||||
// are updated.
|
||||
while let Some(mut entry) = store.load_block_entry(&cur_hash)? {
|
||||
|
||||
if entry.block_number() <= up_to {
|
||||
state = State::Approving;
|
||||
}
|
||||
@@ -326,7 +308,7 @@ pub fn force_approve(
|
||||
entry.approved_bitfield.iter_mut().for_each(|mut b| *b = true);
|
||||
approved_hashes.push(entry.block_hash());
|
||||
store.write_block_entry(entry);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,18 +20,17 @@
|
||||
//! Within that context, things are plain-old-data. Within this module,
|
||||
//! data and logic are intertwined.
|
||||
|
||||
use polkadot_node_primitives::approval::{DelayTranche, RelayVRFStory, AssignmentCert};
|
||||
use polkadot_node_primitives::approval::{AssignmentCert, DelayTranche, RelayVRFStory};
|
||||
use polkadot_primitives::v1::{
|
||||
ValidatorIndex, CandidateReceipt, SessionIndex, GroupIndex, CoreIndex,
|
||||
Hash, CandidateHash, BlockNumber, ValidatorSignature,
|
||||
BlockNumber, CandidateHash, CandidateReceipt, CoreIndex, GroupIndex, Hash, SessionIndex,
|
||||
ValidatorIndex, ValidatorSignature,
|
||||
};
|
||||
use sp_consensus_slots::Slot;
|
||||
|
||||
use bitvec::{order::Lsb0 as BitOrderLsb0, slice::BitSlice, vec::BitVec};
|
||||
use std::collections::BTreeMap;
|
||||
use bitvec::{slice::BitSlice, vec::BitVec, order::Lsb0 as BitOrderLsb0};
|
||||
|
||||
use super::time::Tick;
|
||||
use super::criteria::OurAssignment;
|
||||
use super::{criteria::OurAssignment, time::Tick};
|
||||
|
||||
/// Metadata regarding a specific tranche of assignments for a specific candidate.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@@ -105,11 +104,14 @@ impl ApprovalEntry {
|
||||
}
|
||||
|
||||
// Note that our assignment is triggered. No-op if already triggered.
|
||||
pub fn trigger_our_assignment(&mut self, tick_now: Tick)
|
||||
-> Option<(AssignmentCert, ValidatorIndex, DelayTranche)>
|
||||
{
|
||||
pub fn trigger_our_assignment(
|
||||
&mut self,
|
||||
tick_now: Tick,
|
||||
) -> Option<(AssignmentCert, ValidatorIndex, DelayTranche)> {
|
||||
let our = self.our_assignment.as_mut().and_then(|a| {
|
||||
if a.triggered() { return None }
|
||||
if a.triggered() {
|
||||
return None
|
||||
}
|
||||
a.mark_triggered();
|
||||
|
||||
Some(a.clone())
|
||||
@@ -143,22 +145,16 @@ impl ApprovalEntry {
|
||||
let idx = match self.tranches.iter().position(|t| t.tranche >= tranche) {
|
||||
Some(pos) => {
|
||||
if self.tranches[pos].tranche > tranche {
|
||||
self.tranches.insert(pos, TrancheEntry {
|
||||
tranche: tranche,
|
||||
assignments: Vec::new(),
|
||||
});
|
||||
self.tranches.insert(pos, TrancheEntry { tranche, assignments: Vec::new() });
|
||||
}
|
||||
|
||||
pos
|
||||
}
|
||||
},
|
||||
None => {
|
||||
self.tranches.push(TrancheEntry {
|
||||
tranche: tranche,
|
||||
assignments: Vec::new(),
|
||||
});
|
||||
self.tranches.push(TrancheEntry { tranche, assignments: Vec::new() });
|
||||
|
||||
self.tranches.len() - 1
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
self.tranches[idx].assignments.push((validator_index, tick_now));
|
||||
@@ -168,15 +164,16 @@ impl ApprovalEntry {
|
||||
// Produce a bitvec indicating the assignments of all validators up to and
|
||||
// including `tranche`.
|
||||
pub fn assignments_up_to(&self, tranche: DelayTranche) -> BitVec<BitOrderLsb0, u8> {
|
||||
self.tranches.iter()
|
||||
.take_while(|e| e.tranche <= tranche)
|
||||
.fold(bitvec::bitvec![BitOrderLsb0, u8; 0; self.assignments.len()], |mut a, e| {
|
||||
self.tranches.iter().take_while(|e| e.tranche <= tranche).fold(
|
||||
bitvec::bitvec![BitOrderLsb0, u8; 0; self.assignments.len()],
|
||||
|mut a, e| {
|
||||
for &(v, _) in &e.assignments {
|
||||
a.set(v.0 as _, true);
|
||||
}
|
||||
|
||||
a
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Whether the approval entry is approved
|
||||
@@ -299,11 +296,7 @@ impl CandidateEntry {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn add_approval_entry(
|
||||
&mut self,
|
||||
block_hash: Hash,
|
||||
approval_entry: ApprovalEntry,
|
||||
) {
|
||||
pub fn add_approval_entry(&mut self, block_hash: Hash, approval_entry: ApprovalEntry) {
|
||||
self.block_assignments.insert(block_hash, approval_entry);
|
||||
}
|
||||
}
|
||||
@@ -313,7 +306,11 @@ impl From<crate::approval_db::v1::CandidateEntry> for CandidateEntry {
|
||||
CandidateEntry {
|
||||
candidate: entry.candidate,
|
||||
session: entry.session,
|
||||
block_assignments: entry.block_assignments.into_iter().map(|(h, ae)| (h, ae.into())).collect(),
|
||||
block_assignments: entry
|
||||
.block_assignments
|
||||
.into_iter()
|
||||
.map(|(h, ae)| (h, ae.into()))
|
||||
.collect(),
|
||||
approvals: entry.approvals,
|
||||
}
|
||||
}
|
||||
@@ -324,7 +321,11 @@ impl From<CandidateEntry> for crate::approval_db::v1::CandidateEntry {
|
||||
Self {
|
||||
candidate: entry.candidate,
|
||||
session: entry.session,
|
||||
block_assignments: entry.block_assignments.into_iter().map(|(h, ae)| (h, ae.into())).collect(),
|
||||
block_assignments: entry
|
||||
.block_assignments
|
||||
.into_iter()
|
||||
.map(|(h, ae)| (h, ae.into()))
|
||||
.collect(),
|
||||
approvals: entry.approvals,
|
||||
}
|
||||
}
|
||||
@@ -360,7 +361,9 @@ impl BlockEntry {
|
||||
|
||||
/// Whether a candidate is approved in the bitfield.
|
||||
pub fn is_candidate_approved(&self, candidate_hash: &CandidateHash) -> bool {
|
||||
self.candidates.iter().position(|(_, h)| h == candidate_hash)
|
||||
self.candidates
|
||||
.iter()
|
||||
.position(|(_, h)| h == candidate_hash)
|
||||
.and_then(|p| self.approved_bitfield.get(p).map(|b| *b))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
@@ -372,10 +375,12 @@ impl BlockEntry {
|
||||
|
||||
/// Iterate over all unapproved candidates.
|
||||
pub fn unapproved_candidates(&self) -> impl Iterator<Item = CandidateHash> + '_ {
|
||||
self.approved_bitfield.iter().enumerate().filter_map(move |(i, a)| if !*a {
|
||||
Some(self.candidates[i].1)
|
||||
} else {
|
||||
None
|
||||
self.approved_bitfield.iter().enumerate().filter_map(move |(i, a)| {
|
||||
if !*a {
|
||||
Some(self.candidates[i].1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -385,9 +390,7 @@ impl BlockEntry {
|
||||
/// Panics if the core is already used.
|
||||
#[cfg(test)]
|
||||
pub fn add_candidate(&mut self, core: CoreIndex, candidate_hash: CandidateHash) -> usize {
|
||||
let pos = self.candidates
|
||||
.binary_search_by_key(&core, |(c, _)| *c)
|
||||
.unwrap_err();
|
||||
let pos = self.candidates.binary_search_by_key(&core, |(c, _)| *c).unwrap_err();
|
||||
|
||||
self.candidates.insert(pos, (core, candidate_hash));
|
||||
|
||||
|
||||
@@ -16,34 +16,39 @@
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
use std::time::Duration;
|
||||
use polkadot_overseer::HeadSupportsParachains;
|
||||
use polkadot_primitives::v1::{
|
||||
CoreIndex, GroupIndex, ValidatorSignature, Header, CandidateEvent,
|
||||
};
|
||||
use polkadot_node_subsystem::{ActivatedLeaf, ActiveLeavesUpdate, LeafStatus};
|
||||
use polkadot_node_primitives::approval::{
|
||||
AssignmentCert, AssignmentCertKind, VRFOutput, VRFProof,
|
||||
RELAY_VRF_MODULO_CONTEXT, DelayTranche,
|
||||
AssignmentCert, AssignmentCertKind, DelayTranche, VRFOutput, VRFProof, RELAY_VRF_MODULO_CONTEXT,
|
||||
};
|
||||
use polkadot_node_subsystem::{
|
||||
messages::{AllMessages, ApprovalVotingMessage, AssignmentCheckResult},
|
||||
ActivatedLeaf, ActiveLeavesUpdate, LeafStatus,
|
||||
};
|
||||
use polkadot_node_subsystem_test_helpers as test_helpers;
|
||||
use polkadot_node_subsystem::messages::{AllMessages, ApprovalVotingMessage, AssignmentCheckResult};
|
||||
use polkadot_node_subsystem_util::TimeoutExt;
|
||||
use polkadot_overseer::HeadSupportsParachains;
|
||||
use polkadot_primitives::v1::{CandidateEvent, CoreIndex, GroupIndex, Header, ValidatorSignature};
|
||||
use std::time::Duration;
|
||||
|
||||
use assert_matches::assert_matches;
|
||||
use parking_lot::Mutex;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use sp_keyring::sr25519::Keyring as Sr25519Keyring;
|
||||
use sp_keystore::CryptoStore;
|
||||
use assert_matches::assert_matches;
|
||||
|
||||
use super::import::tests::{
|
||||
BabeEpoch, BabeEpochConfiguration, AllowedSlots, Digest, garbage_vrf, DigestItem, PreDigest,
|
||||
SecondaryVRFPreDigest, CompatibleDigestItem,
|
||||
use std::{
|
||||
pin::Pin,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{
|
||||
approval_db::v1::StoredBlockRange,
|
||||
backend::BackendWriteOp,
|
||||
import::tests::{
|
||||
garbage_vrf, AllowedSlots, BabeEpoch, BabeEpochConfiguration, CompatibleDigestItem, Digest,
|
||||
DigestItem, PreDigest, SecondaryVRFPreDigest,
|
||||
},
|
||||
};
|
||||
use super::approval_db::v1::StoredBlockRange;
|
||||
use super::backend::BackendWriteOp;
|
||||
|
||||
const SLOT_DURATION_MILLIS: u64 = 5000;
|
||||
|
||||
@@ -92,14 +97,8 @@ fn make_sync_oracle(val: bool) -> (TestSyncOracle, TestSyncOracleHandle) {
|
||||
let flag = Arc::new(AtomicBool::new(val));
|
||||
|
||||
(
|
||||
TestSyncOracle {
|
||||
flag: flag.clone(),
|
||||
done_syncing_sender: Arc::new(Mutex::new(Some(tx))),
|
||||
},
|
||||
TestSyncOracleHandle {
|
||||
flag,
|
||||
done_syncing_receiver: rx,
|
||||
}
|
||||
TestSyncOracle { flag: flag.clone(), done_syncing_sender: Arc::new(Mutex::new(Some(tx))) },
|
||||
TestSyncOracleHandle { flag, done_syncing_receiver: rx },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -114,9 +113,7 @@ pub mod test_constants {
|
||||
const DATA_COL: u32 = 0;
|
||||
pub(crate) const NUM_COLUMNS: u32 = 1;
|
||||
|
||||
pub(crate) const TEST_CONFIG: DatabaseConfig = DatabaseConfig {
|
||||
col_data: DATA_COL,
|
||||
};
|
||||
pub(crate) const TEST_CONFIG: DatabaseConfig = DatabaseConfig { col_data: DATA_COL };
|
||||
}
|
||||
|
||||
struct MockSupportsParachains;
|
||||
@@ -175,10 +172,8 @@ impl MockClockInner {
|
||||
fn wakeup_all(&mut self, up_to: Tick) {
|
||||
// This finds the position of the first wakeup after
|
||||
// the given tick, or the end of the map.
|
||||
let drain_up_to = self.wakeups.binary_search_by_key(
|
||||
&(up_to + 1),
|
||||
|w| w.0,
|
||||
).unwrap_or_else(|i| i);
|
||||
let drain_up_to =
|
||||
self.wakeups.binary_search_by_key(&(up_to + 1), |w| w.0).unwrap_or_else(|i| i);
|
||||
|
||||
for (_, wakeup) in self.wakeups.drain(..drain_up_to) {
|
||||
let _ = wakeup.send(());
|
||||
@@ -201,10 +196,7 @@ impl MockClockInner {
|
||||
fn register_wakeup(&mut self, tick: Tick, pre_emptive: bool) -> oneshot::Receiver<()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
let pos = self.wakeups.binary_search_by_key(
|
||||
&tick,
|
||||
|w| w.0,
|
||||
).unwrap_or_else(|i| i);
|
||||
let pos = self.wakeups.binary_search_by_key(&tick, |w| w.0).unwrap_or_else(|i| i);
|
||||
|
||||
self.wakeups.insert(pos, (tick, tx));
|
||||
|
||||
@@ -223,14 +215,18 @@ struct MockAssignmentCriteria<Compute, Check>(Compute, Check);
|
||||
impl<Compute, Check> AssignmentCriteria for MockAssignmentCriteria<Compute, Check>
|
||||
where
|
||||
Compute: Fn() -> HashMap<polkadot_primitives::v1::CoreIndex, criteria::OurAssignment>,
|
||||
Check: Fn() -> Result<DelayTranche, criteria::InvalidAssignment>
|
||||
Check: Fn() -> Result<DelayTranche, criteria::InvalidAssignment>,
|
||||
{
|
||||
fn compute_assignments(
|
||||
&self,
|
||||
_keystore: &LocalKeystore,
|
||||
_relay_vrf_story: polkadot_node_primitives::approval::RelayVRFStory,
|
||||
_config: &criteria::Config,
|
||||
_leaving_cores: Vec<(CandidateHash, polkadot_primitives::v1::CoreIndex, polkadot_primitives::v1::GroupIndex)>,
|
||||
_leaving_cores: Vec<(
|
||||
CandidateHash,
|
||||
polkadot_primitives::v1::CoreIndex,
|
||||
polkadot_primitives::v1::GroupIndex,
|
||||
)>,
|
||||
) -> HashMap<polkadot_primitives::v1::CoreIndex, criteria::OurAssignment> {
|
||||
self.0()
|
||||
}
|
||||
@@ -248,10 +244,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> MockAssignmentCriteria<
|
||||
fn() -> HashMap<polkadot_primitives::v1::CoreIndex, criteria::OurAssignment>,
|
||||
F,
|
||||
> {
|
||||
impl<F>
|
||||
MockAssignmentCriteria<
|
||||
fn() -> HashMap<polkadot_primitives::v1::CoreIndex, criteria::OurAssignment>,
|
||||
F,
|
||||
>
|
||||
{
|
||||
fn check_only(f: F) -> Self {
|
||||
MockAssignmentCriteria(Default::default, f)
|
||||
}
|
||||
@@ -266,10 +264,7 @@ struct TestStore {
|
||||
}
|
||||
|
||||
impl Backend for TestStore {
|
||||
fn load_block_entry(
|
||||
&self,
|
||||
block_hash: &Hash,
|
||||
) -> SubsystemResult<Option<BlockEntry>> {
|
||||
fn load_block_entry(&self, block_hash: &Hash) -> SubsystemResult<Option<BlockEntry>> {
|
||||
Ok(self.block_entries.get(block_hash).cloned())
|
||||
}
|
||||
|
||||
@@ -280,10 +275,7 @@ impl Backend for TestStore {
|
||||
Ok(self.candidate_entries.get(candidate_hash).cloned())
|
||||
}
|
||||
|
||||
fn load_blocks_at_height(
|
||||
&self,
|
||||
height: &BlockNumber,
|
||||
) -> SubsystemResult<Vec<Hash>> {
|
||||
fn load_blocks_at_height(&self, height: &BlockNumber) -> SubsystemResult<Vec<Hash>> {
|
||||
Ok(self.blocks_at_height.get(height).cloned().unwrap_or_default())
|
||||
}
|
||||
|
||||
@@ -300,31 +292,33 @@ impl Backend for TestStore {
|
||||
}
|
||||
|
||||
fn write<I>(&mut self, ops: I) -> SubsystemResult<()>
|
||||
where I: IntoIterator<Item = BackendWriteOp>
|
||||
where
|
||||
I: IntoIterator<Item = BackendWriteOp>,
|
||||
{
|
||||
for op in ops {
|
||||
match op {
|
||||
BackendWriteOp::WriteStoredBlockRange(stored_block_range) => {
|
||||
self.stored_block_range = Some(stored_block_range);
|
||||
}
|
||||
},
|
||||
BackendWriteOp::WriteBlocksAtHeight(h, blocks) => {
|
||||
self.blocks_at_height.insert(h, blocks);
|
||||
}
|
||||
},
|
||||
BackendWriteOp::DeleteBlocksAtHeight(h) => {
|
||||
let _ = self.blocks_at_height.remove(&h);
|
||||
}
|
||||
},
|
||||
BackendWriteOp::WriteBlockEntry(block_entry) => {
|
||||
self.block_entries.insert(block_entry.block_hash(), block_entry);
|
||||
}
|
||||
},
|
||||
BackendWriteOp::DeleteBlockEntry(hash) => {
|
||||
let _ = self.block_entries.remove(&hash);
|
||||
}
|
||||
},
|
||||
BackendWriteOp::WriteCandidateEntry(candidate_entry) => {
|
||||
self.candidate_entries.insert(candidate_entry.candidate_receipt().hash(), candidate_entry);
|
||||
}
|
||||
self.candidate_entries
|
||||
.insert(candidate_entry.candidate_receipt().hash(), candidate_entry);
|
||||
},
|
||||
BackendWriteOp::DeleteCandidateEntry(candidate_hash) => {
|
||||
let _ = self.candidate_entries.remove(&candidate_hash);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,10 +334,7 @@ fn garbage_assignment_cert(kind: AssignmentCertKind) -> AssignmentCert {
|
||||
let (inout, proof, _) = keypair.vrf_sign(ctx.bytes(msg));
|
||||
let out = inout.to_output();
|
||||
|
||||
AssignmentCert {
|
||||
kind,
|
||||
vrf: (VRFOutput(out), VRFProof(proof)),
|
||||
}
|
||||
AssignmentCert { kind, vrf: (VRFOutput(out), VRFProof(proof)) }
|
||||
}
|
||||
|
||||
fn sign_approval(
|
||||
@@ -383,47 +374,40 @@ fn test_harness<T: Future<Output = VirtualOverseer>>(
|
||||
|
||||
let store = TestStore::default();
|
||||
|
||||
let HarnessConfig {
|
||||
tick_start,
|
||||
assigned_tranche,
|
||||
} = config;
|
||||
let HarnessConfig { tick_start, assigned_tranche } = config;
|
||||
|
||||
let clock = Box::new(MockClock::new(tick_start));
|
||||
let subsystem = run(
|
||||
context,
|
||||
ApprovalVotingSubsystem::with_config(
|
||||
Config{
|
||||
col_data: test_constants::TEST_CONFIG.col_data,
|
||||
slot_duration_millis: 100u64,
|
||||
},
|
||||
Config { col_data: test_constants::TEST_CONFIG.col_data, slot_duration_millis: 100u64 },
|
||||
Arc::new(kvdb_memorydb::create(test_constants::NUM_COLUMNS)),
|
||||
Arc::new(keystore),
|
||||
sync_oracle,
|
||||
Metrics::default(),
|
||||
),
|
||||
clock.clone(),
|
||||
Box::new(MockAssignmentCriteria::check_only(move || { Ok(assigned_tranche) })),
|
||||
Box::new(MockAssignmentCriteria::check_only(move || Ok(assigned_tranche))),
|
||||
store,
|
||||
);
|
||||
|
||||
let test_fut = test(TestHarness {
|
||||
virtual_overseer,
|
||||
clock,
|
||||
});
|
||||
let test_fut = test(TestHarness { virtual_overseer, clock });
|
||||
|
||||
futures::pin_mut!(test_fut);
|
||||
futures::pin_mut!(subsystem);
|
||||
|
||||
futures::executor::block_on(future::join(async move {
|
||||
let mut overseer = test_fut.await;
|
||||
overseer_signal(&mut overseer, OverseerSignal::Conclude).await;
|
||||
}, subsystem)).1.unwrap();
|
||||
futures::executor::block_on(future::join(
|
||||
async move {
|
||||
let mut overseer = test_fut.await;
|
||||
overseer_signal(&mut overseer, OverseerSignal::Conclude).await;
|
||||
},
|
||||
subsystem,
|
||||
))
|
||||
.1
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn overseer_send(
|
||||
overseer: &mut VirtualOverseer,
|
||||
msg: FromOverseer<ApprovalVotingMessage>,
|
||||
) {
|
||||
async fn overseer_send(overseer: &mut VirtualOverseer, msg: FromOverseer<ApprovalVotingMessage>) {
|
||||
tracing::trace!("Sending message:\n{:?}", &msg);
|
||||
overseer
|
||||
.send(msg)
|
||||
@@ -432,9 +416,7 @@ async fn overseer_send(
|
||||
.expect(&format!("{:?} is enough for sending messages.", TIMEOUT));
|
||||
}
|
||||
|
||||
async fn overseer_recv(
|
||||
overseer: &mut VirtualOverseer,
|
||||
) -> AllMessages {
|
||||
async fn overseer_recv(overseer: &mut VirtualOverseer) -> AllMessages {
|
||||
let msg = overseer_recv_with_timeout(overseer, TIMEOUT)
|
||||
.await
|
||||
.expect(&format!("{:?} is enough to receive messages.", TIMEOUT));
|
||||
@@ -449,17 +431,11 @@ async fn overseer_recv_with_timeout(
|
||||
timeout: Duration,
|
||||
) -> Option<AllMessages> {
|
||||
tracing::trace!("Waiting for message...");
|
||||
overseer
|
||||
.recv()
|
||||
.timeout(timeout)
|
||||
.await
|
||||
overseer.recv().timeout(timeout).await
|
||||
}
|
||||
|
||||
const TIMEOUT: Duration = Duration::from_millis(2000);
|
||||
async fn overseer_signal(
|
||||
overseer: &mut VirtualOverseer,
|
||||
signal: OverseerSignal,
|
||||
) {
|
||||
async fn overseer_signal(overseer: &mut VirtualOverseer, signal: OverseerSignal) {
|
||||
overseer
|
||||
.send(FromOverseer::Signal(signal))
|
||||
.timeout(TIMEOUT)
|
||||
@@ -471,10 +447,7 @@ async fn overseer_signal(
|
||||
fn blank_subsystem_act_on_bad_block() {
|
||||
let (oracle, handle) = make_sync_oracle(false);
|
||||
test_harness(Default::default(), Box::new(oracle), |test_harness| async move {
|
||||
let TestHarness {
|
||||
mut virtual_overseer,
|
||||
..
|
||||
} = test_harness;
|
||||
let TestHarness { mut virtual_overseer, .. } = test_harness;
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
@@ -484,18 +457,19 @@ fn blank_subsystem_act_on_bad_block() {
|
||||
&mut virtual_overseer,
|
||||
FromOverseer::Communication {
|
||||
msg: ApprovalVotingMessage::CheckAndImportAssignment(
|
||||
IndirectAssignmentCert{
|
||||
IndirectAssignmentCert {
|
||||
block_hash: bad_block_hash.clone(),
|
||||
validator: 0u32.into(),
|
||||
cert: garbage_assignment_cert(
|
||||
AssignmentCertKind::RelayVRFModulo { sample: 0 }
|
||||
),
|
||||
cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo {
|
||||
sample: 0,
|
||||
}),
|
||||
},
|
||||
0u32,
|
||||
tx,
|
||||
)
|
||||
}
|
||||
).await;
|
||||
),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
handle.await_mode_switch().await;
|
||||
|
||||
@@ -516,10 +490,7 @@ fn blank_subsystem_act_on_bad_block() {
|
||||
fn ss_rejects_approval_if_no_block_entry() {
|
||||
let (oracle, _handle) = make_sync_oracle(false);
|
||||
test_harness(Default::default(), Box::new(oracle), |test_harness| async move {
|
||||
let TestHarness {
|
||||
mut virtual_overseer,
|
||||
..
|
||||
} = test_harness;
|
||||
let TestHarness { mut virtual_overseer, .. } = test_harness;
|
||||
|
||||
let block_hash = Hash::repeat_byte(0x01);
|
||||
let candidate_index = 0;
|
||||
@@ -535,7 +506,8 @@ fn ss_rejects_approval_if_no_block_entry() {
|
||||
candidate_hash,
|
||||
session_index,
|
||||
false,
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_matches!(
|
||||
rx.await,
|
||||
@@ -552,10 +524,7 @@ fn ss_rejects_approval_if_no_block_entry() {
|
||||
fn ss_rejects_approval_before_assignment() {
|
||||
let (oracle, _handle) = make_sync_oracle(false);
|
||||
test_harness(Default::default(), Box::new(oracle), |test_harness| async move {
|
||||
let TestHarness {
|
||||
mut virtual_overseer,
|
||||
..
|
||||
} = test_harness;
|
||||
let TestHarness { mut virtual_overseer, .. } = test_harness;
|
||||
|
||||
let block_hash = Hash::repeat_byte(0x01);
|
||||
|
||||
@@ -584,7 +553,8 @@ fn ss_rejects_approval_before_assignment() {
|
||||
candidate_hash,
|
||||
session_index,
|
||||
false,
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_matches!(
|
||||
rx.await,
|
||||
@@ -600,59 +570,49 @@ fn ss_rejects_approval_before_assignment() {
|
||||
#[test]
|
||||
fn ss_rejects_assignment_in_future() {
|
||||
let (oracle, _handle) = make_sync_oracle(false);
|
||||
test_harness(HarnessConfig {
|
||||
tick_start: 0,
|
||||
assigned_tranche: TICK_TOO_FAR_IN_FUTURE as _,
|
||||
..Default::default()
|
||||
}, Box::new(oracle), |test_harness| async move {
|
||||
let TestHarness {
|
||||
mut virtual_overseer,
|
||||
clock,
|
||||
} = test_harness;
|
||||
test_harness(
|
||||
HarnessConfig {
|
||||
tick_start: 0,
|
||||
assigned_tranche: TICK_TOO_FAR_IN_FUTURE as _,
|
||||
..Default::default()
|
||||
},
|
||||
Box::new(oracle),
|
||||
|test_harness| async move {
|
||||
let TestHarness { mut virtual_overseer, clock } = test_harness;
|
||||
|
||||
let block_hash = Hash::repeat_byte(0x01);
|
||||
let candidate_index = 0;
|
||||
let validator = ValidatorIndex(0);
|
||||
let block_hash = Hash::repeat_byte(0x01);
|
||||
let candidate_index = 0;
|
||||
let validator = ValidatorIndex(0);
|
||||
|
||||
// Add block hash 00.
|
||||
ChainBuilder::new()
|
||||
.add_block(block_hash, ChainBuilder::GENESIS_HASH, Slot::from(1), 1)
|
||||
.build(&mut virtual_overseer)
|
||||
.await;
|
||||
// Add block hash 00.
|
||||
ChainBuilder::new()
|
||||
.add_block(block_hash, ChainBuilder::GENESIS_HASH, Slot::from(1), 1)
|
||||
.build(&mut virtual_overseer)
|
||||
.await;
|
||||
|
||||
let rx = cai_assignment(
|
||||
&mut virtual_overseer,
|
||||
block_hash,
|
||||
candidate_index,
|
||||
validator,
|
||||
).await;
|
||||
let rx =
|
||||
cai_assignment(&mut virtual_overseer, block_hash, candidate_index, validator).await;
|
||||
|
||||
assert_eq!(rx.await, Ok(AssignmentCheckResult::TooFarInFuture));
|
||||
assert_eq!(rx.await, Ok(AssignmentCheckResult::TooFarInFuture));
|
||||
|
||||
// Advance clock to make assignment reasonably near.
|
||||
clock.inner.lock().set_tick(1);
|
||||
// Advance clock to make assignment reasonably near.
|
||||
clock.inner.lock().set_tick(1);
|
||||
|
||||
let rx = cai_assignment(
|
||||
&mut virtual_overseer,
|
||||
block_hash,
|
||||
candidate_index,
|
||||
validator,
|
||||
).await;
|
||||
let rx =
|
||||
cai_assignment(&mut virtual_overseer, block_hash, candidate_index, validator).await;
|
||||
|
||||
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted));
|
||||
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted));
|
||||
|
||||
virtual_overseer
|
||||
});
|
||||
virtual_overseer
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ss_accepts_duplicate_assignment() {
|
||||
let (oracle, _handle) = make_sync_oracle(false);
|
||||
test_harness(Default::default(), Box::new(oracle), |test_harness| async move {
|
||||
let TestHarness {
|
||||
mut virtual_overseer,
|
||||
..
|
||||
} = test_harness;
|
||||
let TestHarness { mut virtual_overseer, .. } = test_harness;
|
||||
|
||||
let block_hash = Hash::repeat_byte(0x01);
|
||||
let candidate_index = 0;
|
||||
@@ -664,21 +624,13 @@ fn ss_accepts_duplicate_assignment() {
|
||||
.build(&mut virtual_overseer)
|
||||
.await;
|
||||
|
||||
let rx = cai_assignment(
|
||||
&mut virtual_overseer,
|
||||
block_hash,
|
||||
candidate_index,
|
||||
validator,
|
||||
).await;
|
||||
let rx =
|
||||
cai_assignment(&mut virtual_overseer, block_hash, candidate_index, validator).await;
|
||||
|
||||
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted));
|
||||
|
||||
let rx = cai_assignment(
|
||||
&mut virtual_overseer,
|
||||
block_hash,
|
||||
candidate_index,
|
||||
validator,
|
||||
).await;
|
||||
let rx =
|
||||
cai_assignment(&mut virtual_overseer, block_hash, candidate_index, validator).await;
|
||||
|
||||
assert_eq!(rx.await, Ok(AssignmentCheckResult::AcceptedDuplicate));
|
||||
|
||||
@@ -690,10 +642,7 @@ fn ss_accepts_duplicate_assignment() {
|
||||
fn ss_rejects_assignment_with_unknown_candidate() {
|
||||
let (oracle, _handle) = make_sync_oracle(false);
|
||||
test_harness(Default::default(), Box::new(oracle), |test_harness| async move {
|
||||
let TestHarness {
|
||||
mut virtual_overseer,
|
||||
..
|
||||
} = test_harness;
|
||||
let TestHarness { mut virtual_overseer, .. } = test_harness;
|
||||
|
||||
let block_hash = Hash::repeat_byte(0x01);
|
||||
let candidate_index = 7;
|
||||
@@ -705,16 +654,14 @@ fn ss_rejects_assignment_with_unknown_candidate() {
|
||||
.build(&mut virtual_overseer)
|
||||
.await;
|
||||
|
||||
let rx = cai_assignment(
|
||||
&mut virtual_overseer,
|
||||
block_hash,
|
||||
candidate_index,
|
||||
validator,
|
||||
).await;
|
||||
let rx =
|
||||
cai_assignment(&mut virtual_overseer, block_hash, candidate_index, validator).await;
|
||||
|
||||
assert_eq!(
|
||||
rx.await,
|
||||
Ok(AssignmentCheckResult::Bad(AssignmentCheckError::InvalidCandidateIndex(candidate_index))),
|
||||
Ok(AssignmentCheckResult::Bad(AssignmentCheckError::InvalidCandidateIndex(
|
||||
candidate_index
|
||||
))),
|
||||
);
|
||||
|
||||
virtual_overseer
|
||||
@@ -725,10 +672,7 @@ fn ss_rejects_assignment_with_unknown_candidate() {
|
||||
fn ss_accepts_and_imports_approval_after_assignment() {
|
||||
let (oracle, _handle) = make_sync_oracle(false);
|
||||
test_harness(Default::default(), Box::new(oracle), |test_harness| async move {
|
||||
let TestHarness {
|
||||
mut virtual_overseer,
|
||||
..
|
||||
} = test_harness;
|
||||
let TestHarness { mut virtual_overseer, .. } = test_harness;
|
||||
|
||||
let block_hash = Hash::repeat_byte(0x01);
|
||||
|
||||
@@ -749,12 +693,8 @@ fn ss_accepts_and_imports_approval_after_assignment() {
|
||||
.build(&mut virtual_overseer)
|
||||
.await;
|
||||
|
||||
let rx = cai_assignment(
|
||||
&mut virtual_overseer,
|
||||
block_hash,
|
||||
candidate_index,
|
||||
validator,
|
||||
).await;
|
||||
let rx =
|
||||
cai_assignment(&mut virtual_overseer, block_hash, candidate_index, validator).await;
|
||||
|
||||
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted));
|
||||
|
||||
@@ -766,7 +706,8 @@ fn ss_accepts_and_imports_approval_after_assignment() {
|
||||
candidate_hash,
|
||||
session_index,
|
||||
true,
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(rx.await, Ok(ApprovalCheckResult::Accepted));
|
||||
|
||||
@@ -777,10 +718,7 @@ fn ss_accepts_and_imports_approval_after_assignment() {
|
||||
fn ss_assignment_import_updates_candidate_entry_and_schedules_wakeup() {
|
||||
let (oracle, _handle) = make_sync_oracle(false);
|
||||
test_harness(Default::default(), Box::new(oracle), |test_harness| async move {
|
||||
let TestHarness {
|
||||
mut virtual_overseer,
|
||||
..
|
||||
} = test_harness;
|
||||
let TestHarness { mut virtual_overseer, .. } = test_harness;
|
||||
|
||||
let block_hash = Hash::repeat_byte(0x01);
|
||||
|
||||
@@ -800,12 +738,8 @@ fn ss_assignment_import_updates_candidate_entry_and_schedules_wakeup() {
|
||||
.build(&mut virtual_overseer)
|
||||
.await;
|
||||
|
||||
let rx = cai_assignment(
|
||||
&mut virtual_overseer,
|
||||
block_hash,
|
||||
candidate_index,
|
||||
validator,
|
||||
).await;
|
||||
let rx =
|
||||
cai_assignment(&mut virtual_overseer, block_hash, candidate_index, validator).await;
|
||||
|
||||
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted));
|
||||
|
||||
@@ -831,16 +765,12 @@ async fn cai_approval(
|
||||
overseer,
|
||||
FromOverseer::Communication {
|
||||
msg: ApprovalVotingMessage::CheckAndImportApproval(
|
||||
IndirectSignedApprovalVote {
|
||||
block_hash,
|
||||
candidate_index,
|
||||
validator,
|
||||
signature,
|
||||
},
|
||||
IndirectSignedApprovalVote { block_hash, candidate_index, validator, signature },
|
||||
tx,
|
||||
),
|
||||
}
|
||||
).await;
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
if expect_coordinator {
|
||||
assert_matches!(
|
||||
@@ -870,17 +800,14 @@ async fn cai_assignment(
|
||||
IndirectAssignmentCert {
|
||||
block_hash,
|
||||
validator,
|
||||
cert: garbage_assignment_cert(
|
||||
AssignmentCertKind::RelayVRFModulo {
|
||||
sample: 0,
|
||||
},
|
||||
),
|
||||
cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0 }),
|
||||
},
|
||||
candidate_index,
|
||||
tx,
|
||||
),
|
||||
}
|
||||
).await;
|
||||
},
|
||||
)
|
||||
.await;
|
||||
rx
|
||||
}
|
||||
|
||||
@@ -889,16 +816,13 @@ struct ChainBuilder {
|
||||
blocks_at_height: BTreeMap<u32, Vec<Hash>>,
|
||||
}
|
||||
|
||||
|
||||
impl ChainBuilder {
|
||||
const GENESIS_HASH: Hash = Hash::repeat_byte(0xff);
|
||||
const GENESIS_PARENT_HASH: Hash = Hash::repeat_byte(0x00);
|
||||
|
||||
pub fn new() -> Self {
|
||||
let mut builder = Self {
|
||||
blocks_by_hash: HashMap::new(),
|
||||
blocks_at_height: BTreeMap::new(),
|
||||
};
|
||||
let mut builder =
|
||||
Self { blocks_by_hash: HashMap::new(), blocks_at_height: BTreeMap::new() };
|
||||
builder.add_block_inner(Self::GENESIS_HASH, Self::GENESIS_PARENT_HASH, Slot::from(0), 0);
|
||||
builder
|
||||
}
|
||||
@@ -912,7 +836,10 @@ impl ChainBuilder {
|
||||
) -> &'a mut Self {
|
||||
assert!(number != 0, "cannot add duplicate genesis block");
|
||||
assert!(hash != Self::GENESIS_HASH, "cannot add block with genesis hash");
|
||||
assert!(parent_hash != Self::GENESIS_PARENT_HASH, "cannot add block with genesis parent hash");
|
||||
assert!(
|
||||
parent_hash != Self::GENESIS_PARENT_HASH,
|
||||
"cannot add block with genesis parent hash"
|
||||
);
|
||||
assert!(self.blocks_by_hash.len() < u8::MAX.into());
|
||||
self.add_block_inner(hash, parent_hash, slot, number)
|
||||
}
|
||||
@@ -927,7 +854,8 @@ impl ChainBuilder {
|
||||
let header = ChainBuilder::make_header(parent_hash, slot, number);
|
||||
assert!(
|
||||
self.blocks_by_hash.insert(hash, header).is_none(),
|
||||
"block with hash {:?} already exists", hash,
|
||||
"block with hash {:?} already exists",
|
||||
hash,
|
||||
);
|
||||
self.blocks_at_height.entry(number).or_insert_with(Vec::new).push(hash);
|
||||
self
|
||||
@@ -939,7 +867,8 @@ impl ChainBuilder {
|
||||
let mut cur_hash = *hash;
|
||||
let mut ancestry = Vec::new();
|
||||
while cur_hash != Self::GENESIS_PARENT_HASH {
|
||||
let cur_header = self.blocks_by_hash.get(&cur_hash).expect("chain is not contiguous");
|
||||
let cur_header =
|
||||
self.blocks_by_hash.get(&cur_hash).expect("chain is not contiguous");
|
||||
ancestry.push((cur_hash, cur_header.clone()));
|
||||
cur_hash = cur_header.parent_hash;
|
||||
}
|
||||
@@ -950,21 +879,12 @@ impl ChainBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
fn make_header(
|
||||
parent_hash: Hash,
|
||||
slot: Slot,
|
||||
number: u32,
|
||||
) -> Header {
|
||||
fn make_header(parent_hash: Hash, slot: Slot, number: u32) -> Header {
|
||||
let digest = {
|
||||
let mut digest = Digest::default();
|
||||
let (vrf_output, vrf_proof) = garbage_vrf();
|
||||
digest.push(DigestItem::babe_pre_digest(PreDigest::SecondaryVRF(
|
||||
SecondaryVRFPreDigest {
|
||||
authority_index: 0,
|
||||
slot,
|
||||
vrf_output,
|
||||
vrf_proof,
|
||||
}
|
||||
SecondaryVRFPreDigest { authority_index: 0, slot, vrf_output, vrf_proof },
|
||||
)));
|
||||
digest
|
||||
};
|
||||
@@ -976,8 +896,8 @@ impl ChainBuilder {
|
||||
state_root: Default::default(),
|
||||
parent_hash,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn import_block(
|
||||
overseer: &mut VirtualOverseer,
|
||||
@@ -1003,13 +923,16 @@ async fn import_block(
|
||||
let (new_head, new_header) = &hashes[hashes.len() - 1];
|
||||
overseer_send(
|
||||
overseer,
|
||||
FromOverseer::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(ActivatedLeaf {
|
||||
hash: *new_head,
|
||||
number: session,
|
||||
status: LeafStatus::Fresh,
|
||||
span: Arc::new(jaeger::Span::Disabled),
|
||||
})),
|
||||
)).await;
|
||||
FromOverseer::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(
|
||||
ActivatedLeaf {
|
||||
hash: *new_head,
|
||||
number: session,
|
||||
status: LeafStatus::Fresh,
|
||||
span: Arc::new(jaeger::Span::Disabled),
|
||||
},
|
||||
))),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_matches!(
|
||||
overseer_recv(overseer).await,
|
||||
@@ -1070,22 +993,21 @@ async fn import_block(
|
||||
let (hash, header) = hashes[i as usize].clone();
|
||||
assert_eq!(hash, *new_head);
|
||||
h_tx.send(Ok(Some(header))).unwrap();
|
||||
}
|
||||
},
|
||||
AllMessages::ChainApi(ChainApiMessage::Ancestors {
|
||||
hash,
|
||||
k,
|
||||
response_channel,
|
||||
}) => {
|
||||
assert_eq!(hash, *new_head);
|
||||
assert_eq!(k as u32, session-1);
|
||||
assert_eq!(k as u32, session - 1);
|
||||
let history: Vec<Hash> = hashes.iter().map(|v| v.0).take(k).collect();
|
||||
response_channel.send(Ok(history)).unwrap();
|
||||
}
|
||||
_ => unreachable!{},
|
||||
},
|
||||
_ => unreachable! {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if session > 0 {
|
||||
@@ -1184,10 +1106,7 @@ fn linear_import_act_on_leaf() {
|
||||
|
||||
let (oracle, _handle) = make_sync_oracle(false);
|
||||
test_harness(Default::default(), Box::new(oracle), |test_harness| async move {
|
||||
let TestHarness {
|
||||
mut virtual_overseer,
|
||||
..
|
||||
} = test_harness;
|
||||
let TestHarness { mut virtual_overseer, .. } = test_harness;
|
||||
|
||||
let mut head: Hash = ChainBuilder::GENESIS_HASH;
|
||||
let mut builder = ChainBuilder::new();
|
||||
@@ -1197,7 +1116,7 @@ fn linear_import_act_on_leaf() {
|
||||
let hash = Hash::repeat_byte(i as u8);
|
||||
builder.add_block(hash, head, slot, i);
|
||||
head = hash;
|
||||
}
|
||||
}
|
||||
|
||||
builder.build(&mut virtual_overseer).await;
|
||||
|
||||
@@ -1207,18 +1126,19 @@ fn linear_import_act_on_leaf() {
|
||||
&mut virtual_overseer,
|
||||
FromOverseer::Communication {
|
||||
msg: ApprovalVotingMessage::CheckAndImportAssignment(
|
||||
IndirectAssignmentCert{
|
||||
IndirectAssignmentCert {
|
||||
block_hash: head,
|
||||
validator: 0u32.into(),
|
||||
cert: garbage_assignment_cert(
|
||||
AssignmentCertKind::RelayVRFModulo { sample: 0 }
|
||||
),
|
||||
cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo {
|
||||
sample: 0,
|
||||
}),
|
||||
},
|
||||
0u32,
|
||||
tx,
|
||||
)
|
||||
}
|
||||
).await;
|
||||
),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted));
|
||||
|
||||
@@ -1232,10 +1152,7 @@ fn forkful_import_at_same_height_act_on_leaf() {
|
||||
|
||||
let (oracle, _handle) = make_sync_oracle(false);
|
||||
test_harness(Default::default(), Box::new(oracle), |test_harness| async move {
|
||||
let TestHarness {
|
||||
mut virtual_overseer,
|
||||
..
|
||||
} = test_harness;
|
||||
let TestHarness { mut virtual_overseer, .. } = test_harness;
|
||||
|
||||
let mut head: Hash = ChainBuilder::GENESIS_HASH;
|
||||
let mut builder = ChainBuilder::new();
|
||||
@@ -1252,7 +1169,7 @@ fn forkful_import_at_same_height_act_on_leaf() {
|
||||
let slot = Slot::from(session as u64);
|
||||
let hash = Hash::repeat_byte(session as u8 + i);
|
||||
builder.add_block(hash, head, slot, session);
|
||||
}
|
||||
}
|
||||
builder.build(&mut virtual_overseer).await;
|
||||
|
||||
for head in forks.into_iter() {
|
||||
@@ -1262,18 +1179,19 @@ fn forkful_import_at_same_height_act_on_leaf() {
|
||||
&mut virtual_overseer,
|
||||
FromOverseer::Communication {
|
||||
msg: ApprovalVotingMessage::CheckAndImportAssignment(
|
||||
IndirectAssignmentCert{
|
||||
IndirectAssignmentCert {
|
||||
block_hash: head,
|
||||
validator: 0u32.into(),
|
||||
cert: garbage_assignment_cert(
|
||||
AssignmentCertKind::RelayVRFModulo { sample: 0 }
|
||||
),
|
||||
cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo {
|
||||
sample: 0,
|
||||
}),
|
||||
},
|
||||
0u32,
|
||||
tx,
|
||||
)
|
||||
}
|
||||
).await;
|
||||
),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(rx.await, Ok(AssignmentCheckResult::Accepted));
|
||||
}
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
|
||||
//! Time utilities for approval voting.
|
||||
|
||||
use futures::prelude::*;
|
||||
use polkadot_node_primitives::approval::DelayTranche;
|
||||
use sp_consensus_slots::Slot;
|
||||
use futures::prelude::*;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use std::pin::Pin;
|
||||
use std::{
|
||||
pin::Pin,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
const TICK_DURATION_MILLIS: u64 = 500;
|
||||
|
||||
|
||||
@@ -16,41 +16,36 @@
|
||||
|
||||
//! Implements a `AvailabilityStoreSubsystem`.
|
||||
|
||||
#![recursion_limit="256"]
|
||||
#![recursion_limit = "256"]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use std::collections::{HashMap, HashSet, BTreeSet};
|
||||
use std::io;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, SystemTime, SystemTimeError, UNIX_EPOCH};
|
||||
use std::{
|
||||
collections::{BTreeSet, HashMap, HashSet},
|
||||
io,
|
||||
sync::Arc,
|
||||
time::{Duration, SystemTime, SystemTimeError, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use parity_scale_codec::{Encode, Decode, Input, Error as CodecError};
|
||||
use futures::{select, channel::oneshot, future, FutureExt};
|
||||
use futures::{channel::oneshot, future, select, FutureExt};
|
||||
use futures_timer::Delay;
|
||||
use kvdb::{KeyValueDB, DBTransaction};
|
||||
use kvdb::{DBTransaction, KeyValueDB};
|
||||
use parity_scale_codec::{Decode, Encode, Error as CodecError, Input};
|
||||
|
||||
use polkadot_primitives::v1::{
|
||||
Hash, BlockNumber, CandidateEvent, ValidatorIndex, CandidateHash,
|
||||
CandidateReceipt, Header,
|
||||
};
|
||||
use polkadot_node_primitives::{
|
||||
ErasureChunk, AvailableData,
|
||||
};
|
||||
use polkadot_subsystem::{
|
||||
FromOverseer, OverseerSignal, SubsystemError,
|
||||
SubsystemContext, SpawnedSubsystem,
|
||||
overseer,
|
||||
ActiveLeavesUpdate,
|
||||
errors::{ChainApiError, RuntimeApiError},
|
||||
};
|
||||
use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec};
|
||||
use polkadot_node_primitives::{AvailableData, ErasureChunk};
|
||||
use polkadot_node_subsystem_util::{
|
||||
self as util,
|
||||
metrics::{self, prometheus},
|
||||
};
|
||||
use polkadot_subsystem::messages::{
|
||||
AvailabilityStoreMessage, ChainApiMessage,
|
||||
use polkadot_primitives::v1::{
|
||||
BlockNumber, CandidateEvent, CandidateHash, CandidateReceipt, Hash, Header, ValidatorIndex,
|
||||
};
|
||||
use polkadot_subsystem::{
|
||||
errors::{ChainApiError, RuntimeApiError},
|
||||
messages::{AvailabilityStoreMessage, ChainApiMessage},
|
||||
overseer, ActiveLeavesUpdate, FromOverseer, OverseerSignal, SpawnedSubsystem, SubsystemContext,
|
||||
SubsystemError,
|
||||
};
|
||||
use bitvec::{vec::BitVec, order::Lsb0 as BitOrderLsb0};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -126,7 +121,9 @@ impl Encode for BEBlockNumber {
|
||||
|
||||
impl Decode for BEBlockNumber {
|
||||
fn decode<I: Input>(value: &mut I) -> Result<Self, CodecError> {
|
||||
<[u8; std::mem::size_of::<BlockNumber>()]>::decode(value).map(BlockNumber::from_be_bytes).map(Self)
|
||||
<[u8; std::mem::size_of::<BlockNumber>()]>::decode(value)
|
||||
.map(BlockNumber::from_be_bytes)
|
||||
.map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +140,7 @@ enum State {
|
||||
Unfinalized(BETimestamp, Vec<(BEBlockNumber, Hash)>),
|
||||
/// Candidate data has appeared in a finalized block and did so at the given time.
|
||||
#[codec(index = 2)]
|
||||
Finalized(BETimestamp)
|
||||
Finalized(BETimestamp),
|
||||
}
|
||||
|
||||
// Meta information about a candidate.
|
||||
@@ -163,12 +160,12 @@ fn query_inner<D: Decode>(
|
||||
Ok(Some(raw)) => {
|
||||
let res = D::decode(&mut &raw[..])?;
|
||||
Ok(Some(res))
|
||||
}
|
||||
},
|
||||
Ok(None) => Ok(None),
|
||||
Err(e) => {
|
||||
tracing::warn!(target: LOG_TARGET, err = ?e, "Error reading from the availability store");
|
||||
Err(e.into())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,11 +190,7 @@ fn load_available_data(
|
||||
query_inner(db, config.col_data, &key)
|
||||
}
|
||||
|
||||
fn delete_available_data(
|
||||
tx: &mut DBTransaction,
|
||||
config: &Config,
|
||||
hash: &CandidateHash,
|
||||
) {
|
||||
fn delete_available_data(tx: &mut DBTransaction, config: &Config, hash: &CandidateHash) {
|
||||
let key = (AVAILABLE_PREFIX, hash).encode();
|
||||
|
||||
tx.delete(config.col_data, &key[..])
|
||||
@@ -247,12 +240,7 @@ fn load_meta(
|
||||
query_inner(db, config.col_meta, &key)
|
||||
}
|
||||
|
||||
fn write_meta(
|
||||
tx: &mut DBTransaction,
|
||||
config: &Config,
|
||||
hash: &CandidateHash,
|
||||
meta: &CandidateMeta,
|
||||
) {
|
||||
fn write_meta(tx: &mut DBTransaction, config: &Config, hash: &CandidateHash, meta: &CandidateMeta) {
|
||||
let key = (META_PREFIX, hash).encode();
|
||||
|
||||
tx.put_vec(config.col_meta, &key, meta.encode());
|
||||
@@ -263,11 +251,7 @@ fn delete_meta(tx: &mut DBTransaction, config: &Config, hash: &CandidateHash) {
|
||||
tx.delete(config.col_meta, &key[..])
|
||||
}
|
||||
|
||||
fn delete_unfinalized_height(
|
||||
tx: &mut DBTransaction,
|
||||
config: &Config,
|
||||
block_number: BlockNumber,
|
||||
) {
|
||||
fn delete_unfinalized_height(tx: &mut DBTransaction, config: &Config, block_number: BlockNumber) {
|
||||
let prefix = (UNFINALIZED_PREFIX, BEBlockNumber(block_number)).encode();
|
||||
tx.delete_prefix(config.col_meta, &prefix);
|
||||
}
|
||||
@@ -279,12 +263,8 @@ fn delete_unfinalized_inclusion(
|
||||
block_hash: &Hash,
|
||||
candidate_hash: &CandidateHash,
|
||||
) {
|
||||
let key = (
|
||||
UNFINALIZED_PREFIX,
|
||||
BEBlockNumber(block_number),
|
||||
block_hash,
|
||||
candidate_hash,
|
||||
).encode();
|
||||
let key =
|
||||
(UNFINALIZED_PREFIX, BEBlockNumber(block_number), block_hash, candidate_hash).encode();
|
||||
|
||||
tx.delete(config.col_meta, &key[..]);
|
||||
}
|
||||
@@ -338,7 +318,7 @@ fn pruning_range(now: impl Into<BETimestamp>) -> (Vec<u8>, Vec<u8>) {
|
||||
|
||||
fn decode_unfinalized_key(s: &[u8]) -> Result<(BlockNumber, Hash, CandidateHash), CodecError> {
|
||||
if !s.starts_with(UNFINALIZED_PREFIX) {
|
||||
return Err("missing magic string".into());
|
||||
return Err("missing magic string".into())
|
||||
}
|
||||
|
||||
<(BEBlockNumber, Hash, CandidateHash)>::decode(&mut &s[UNFINALIZED_PREFIX.len()..])
|
||||
@@ -347,7 +327,7 @@ fn decode_unfinalized_key(s: &[u8]) -> Result<(BlockNumber, Hash, CandidateHash)
|
||||
|
||||
fn decode_pruning_key(s: &[u8]) -> Result<(Duration, CandidateHash), CodecError> {
|
||||
if !s.starts_with(PRUNE_BY_TIME_PREFIX) {
|
||||
return Err("missing magic string".into());
|
||||
return Err("missing magic string".into())
|
||||
}
|
||||
|
||||
<(BETimestamp, CandidateHash)>::decode(&mut &s[PRUNE_BY_TIME_PREFIX.len()..])
|
||||
@@ -389,8 +369,8 @@ impl Error {
|
||||
fn trace(&self) {
|
||||
match self {
|
||||
// don't spam the log with spurious errors
|
||||
Self::RuntimeApi(_) |
|
||||
Self::Oneshot(_) => tracing::debug!(target: LOG_TARGET, err = ?self),
|
||||
Self::RuntimeApi(_) | Self::Oneshot(_) =>
|
||||
tracing::debug!(target: LOG_TARGET, err = ?self),
|
||||
// it's worth reporting otherwise
|
||||
_ => tracing::warn!(target: LOG_TARGET, err = ?self),
|
||||
}
|
||||
@@ -457,11 +437,7 @@ pub struct AvailabilityStoreSubsystem {
|
||||
|
||||
impl AvailabilityStoreSubsystem {
|
||||
/// Create a new `AvailabilityStoreSubsystem` with a given config on disk.
|
||||
pub fn new(
|
||||
db: Arc<dyn KeyValueDB>,
|
||||
config: Config,
|
||||
metrics: Metrics,
|
||||
) -> Self {
|
||||
pub fn new(db: Arc<dyn KeyValueDB>, config: Config, metrics: Metrics) -> Self {
|
||||
Self::with_pruning_config_and_clock(
|
||||
db,
|
||||
config,
|
||||
@@ -530,14 +506,9 @@ where
|
||||
Context: overseer::SubsystemContext<Message = AvailabilityStoreMessage>,
|
||||
{
|
||||
fn start(self, ctx: Context) -> SpawnedSubsystem {
|
||||
let future = run(self, ctx)
|
||||
.map(|_| Ok(()))
|
||||
.boxed();
|
||||
let future = run(self, ctx).map(|_| Ok(())).boxed();
|
||||
|
||||
SpawnedSubsystem {
|
||||
name: "availability-store-subsystem",
|
||||
future,
|
||||
}
|
||||
SpawnedSubsystem { name: "availability-store-subsystem", future }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -555,12 +526,12 @@ where
|
||||
e.trace();
|
||||
|
||||
if let Error::Subsystem(SubsystemError::Context(_)) = e {
|
||||
break;
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
Ok(true) => {
|
||||
tracing::info!(target: LOG_TARGET, "received `Conclude` signal, exiting");
|
||||
break;
|
||||
break
|
||||
},
|
||||
Ok(false) => continue,
|
||||
}
|
||||
@@ -571,8 +542,7 @@ async fn run_iteration<Context>(
|
||||
ctx: &mut Context,
|
||||
subsystem: &mut AvailabilityStoreSubsystem,
|
||||
mut next_pruning: &mut future::Fuse<Delay>,
|
||||
)
|
||||
-> Result<bool, Error>
|
||||
) -> Result<bool, Error>
|
||||
where
|
||||
Context: SubsystemContext<Message = AvailabilityStoreMessage>,
|
||||
Context: overseer::SubsystemContext<Message = AvailabilityStoreMessage>,
|
||||
@@ -634,9 +604,7 @@ where
|
||||
let block_header = {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
ctx.send_message(
|
||||
ChainApiMessage::BlockHeader(activated, tx)
|
||||
).await;
|
||||
ctx.send_message(ChainApiMessage::BlockHeader(activated, tx)).await;
|
||||
|
||||
match rx.await?? {
|
||||
None => return Ok(()),
|
||||
@@ -647,13 +615,12 @@ where
|
||||
|
||||
let new_blocks = util::determine_new_blocks(
|
||||
ctx.sender(),
|
||||
|hash| -> Result<bool, Error> {
|
||||
Ok(subsystem.known_blocks.is_known(hash))
|
||||
},
|
||||
|hash| -> Result<bool, Error> { Ok(subsystem.known_blocks.is_known(hash)) },
|
||||
activated,
|
||||
&block_header,
|
||||
subsystem.finalized_number.unwrap_or(block_number.saturating_sub(1)),
|
||||
).await?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
// determine_new_blocks is descending in block height
|
||||
for (hash, header) in new_blocks.into_iter().rev() {
|
||||
@@ -669,7 +636,8 @@ where
|
||||
now,
|
||||
hash,
|
||||
header,
|
||||
).await?;
|
||||
)
|
||||
.await?;
|
||||
subsystem.known_blocks.insert(hash, block_number);
|
||||
subsystem.db.write(tx)?;
|
||||
}
|
||||
@@ -691,18 +659,12 @@ where
|
||||
Context: SubsystemContext<Message = AvailabilityStoreMessage>,
|
||||
Context: overseer::SubsystemContext<Message = AvailabilityStoreMessage>,
|
||||
{
|
||||
|
||||
let candidate_events = util::request_candidate_events(
|
||||
hash,
|
||||
ctx.sender(),
|
||||
).await.await??;
|
||||
let candidate_events = util::request_candidate_events(hash, ctx.sender()).await.await??;
|
||||
|
||||
// We need to request the number of validators based on the parent state,
|
||||
// as that is the number of validators used to create this block.
|
||||
let n_validators = util::request_validators(
|
||||
header.parent_hash,
|
||||
ctx.sender(),
|
||||
).await.await??.len();
|
||||
let n_validators =
|
||||
util::request_validators(header.parent_hash, ctx.sender()).await.await??.len();
|
||||
|
||||
for event in candidate_events {
|
||||
match event {
|
||||
@@ -716,7 +678,7 @@ where
|
||||
n_validators,
|
||||
receipt,
|
||||
)?;
|
||||
}
|
||||
},
|
||||
CandidateEvent::CandidateIncluded(receipt, _head, _core_index, _group_index) => {
|
||||
note_block_included(
|
||||
db,
|
||||
@@ -726,8 +688,8 @@ where
|
||||
(header.number, hash),
|
||||
receipt,
|
||||
)?;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -745,11 +707,7 @@ fn note_block_backed(
|
||||
) -> Result<(), Error> {
|
||||
let candidate_hash = candidate.hash();
|
||||
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
?candidate_hash,
|
||||
"Candidate backed",
|
||||
);
|
||||
tracing::debug!(target: LOG_TARGET, ?candidate_hash, "Candidate backed",);
|
||||
|
||||
if load_meta(db, config, &candidate_hash)?.is_none() {
|
||||
let meta = CandidateMeta {
|
||||
@@ -771,7 +729,7 @@ fn note_block_included(
|
||||
db: &Arc<dyn KeyValueDB>,
|
||||
db_transaction: &mut DBTransaction,
|
||||
config: &Config,
|
||||
pruning_config:&PruningConfig,
|
||||
pruning_config: &PruningConfig,
|
||||
block: (BlockNumber, Hash),
|
||||
candidate: CandidateReceipt,
|
||||
) -> Result<(), Error> {
|
||||
@@ -786,15 +744,11 @@ fn note_block_included(
|
||||
?candidate_hash,
|
||||
"Candidate included without being backed?",
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(mut meta) => {
|
||||
let be_block = (BEBlockNumber(block.0), block.1);
|
||||
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
?candidate_hash,
|
||||
"Candidate included",
|
||||
);
|
||||
tracing::debug!(target: LOG_TARGET, ?candidate_hash, "Candidate included",);
|
||||
|
||||
meta.state = match meta.state {
|
||||
State::Unavailable(at) => {
|
||||
@@ -803,20 +757,20 @@ fn note_block_included(
|
||||
delete_pruning_key(db_transaction, config, prune_at, &candidate_hash);
|
||||
|
||||
State::Unfinalized(at, vec![be_block])
|
||||
}
|
||||
},
|
||||
State::Unfinalized(at, mut within) => {
|
||||
if let Err(i) = within.binary_search(&be_block) {
|
||||
within.insert(i, be_block);
|
||||
State::Unfinalized(at, within)
|
||||
} else {
|
||||
return Ok(());
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
},
|
||||
State::Finalized(_at) => {
|
||||
// This should never happen as a candidate would have to be included after
|
||||
// finality.
|
||||
return Ok(())
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
write_unfinalized_block_contains(
|
||||
@@ -827,7 +781,7 @@ fn note_block_included(
|
||||
&candidate_hash,
|
||||
);
|
||||
write_meta(db_transaction, config, &candidate_hash, &meta);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -837,9 +791,9 @@ macro_rules! peek_num {
|
||||
($iter:ident) => {
|
||||
match $iter.peek() {
|
||||
Some((k, _)) => decode_unfinalized_key(&k[..]).ok().map(|(b, _, _)| b),
|
||||
None => None
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async fn process_block_finalized<Context>(
|
||||
@@ -863,7 +817,9 @@ where
|
||||
// as it is not `Send`. That is why we create the iterator once within this loop, drop it,
|
||||
// do an asynchronous request, and then instantiate the exact same iterator again.
|
||||
let batch_num = {
|
||||
let mut iter = subsystem.db.iter_with_prefix(subsystem.config.col_meta, &start_prefix)
|
||||
let mut iter = subsystem
|
||||
.db
|
||||
.iter_with_prefix(subsystem.config.col_meta, &start_prefix)
|
||||
.take_while(|(k, _)| &k[..] < &end_prefix[..])
|
||||
.peekable();
|
||||
|
||||
@@ -873,7 +829,9 @@ where
|
||||
}
|
||||
};
|
||||
|
||||
if batch_num < next_possible_batch { continue } // sanity.
|
||||
if batch_num < next_possible_batch {
|
||||
continue
|
||||
} // sanity.
|
||||
next_possible_batch = batch_num + 1;
|
||||
|
||||
let batch_finalized_hash = if batch_num == finalized_number {
|
||||
@@ -884,19 +842,22 @@ where
|
||||
|
||||
match rx.await?? {
|
||||
None => {
|
||||
tracing::warn!(target: LOG_TARGET,
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
"Availability store was informed that block #{} is finalized, \
|
||||
but chain API has no finalized hash.",
|
||||
batch_num,
|
||||
);
|
||||
|
||||
break
|
||||
}
|
||||
},
|
||||
Some(h) => h,
|
||||
}
|
||||
};
|
||||
|
||||
let iter = subsystem.db.iter_with_prefix(subsystem.config.col_meta, &start_prefix)
|
||||
let iter = subsystem
|
||||
.db
|
||||
.iter_with_prefix(subsystem.config.col_meta, &start_prefix)
|
||||
.take_while(|(k, _)| &k[..] < &end_prefix[..])
|
||||
.peekable();
|
||||
|
||||
@@ -907,13 +868,7 @@ where
|
||||
|
||||
delete_unfinalized_height(&mut db_transaction, &subsystem.config, batch_num);
|
||||
|
||||
update_blocks_at_finalized_height(
|
||||
&subsystem,
|
||||
&mut db_transaction,
|
||||
batch,
|
||||
batch_num,
|
||||
now,
|
||||
)?;
|
||||
update_blocks_at_finalized_height(&subsystem, &mut db_transaction, batch, batch_num, now)?;
|
||||
|
||||
// We need to write at the end of the loop so the prefix iterator doesn't pick up the same values again
|
||||
// in the next iteration. Another unfortunate effect of having to re-initialize the iterator.
|
||||
@@ -936,14 +891,14 @@ fn load_all_at_finalized_height(
|
||||
// Load all candidates that were included at this height.
|
||||
loop {
|
||||
match peek_num!(iter) {
|
||||
None => break, // end of iterator.
|
||||
None => break, // end of iterator.
|
||||
Some(n) if n != block_number => break, // end of batch.
|
||||
_ => {}
|
||||
_ => {},
|
||||
}
|
||||
|
||||
let (k, _v) = iter.next().expect("`peek` used to check non-empty; qed");
|
||||
let (_, block_hash, candidate_hash) = decode_unfinalized_key(&k[..])
|
||||
.expect("`peek_num` checks validity of key; qed");
|
||||
let (_, block_hash, candidate_hash) =
|
||||
decode_unfinalized_key(&k[..]).expect("`peek_num` checks validity of key; qed");
|
||||
|
||||
if block_hash == finalized_hash {
|
||||
candidates.insert(candidate_hash, true);
|
||||
@@ -971,8 +926,8 @@ fn update_blocks_at_finalized_height(
|
||||
candidate_hash,
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
continue
|
||||
},
|
||||
Some(c) => c,
|
||||
};
|
||||
|
||||
@@ -985,7 +940,7 @@ fn update_blocks_at_finalized_height(
|
||||
// iterating over the candidate here indicates that `State` should
|
||||
// be `Unfinalized`.
|
||||
delete_pruning_key(db_transaction, &subsystem.config, at, &candidate_hash);
|
||||
}
|
||||
},
|
||||
State::Unfinalized(_, blocks) => {
|
||||
for (block_num, block_hash) in blocks.iter().cloned() {
|
||||
// this exact height is all getting cleared out anyway.
|
||||
@@ -999,7 +954,7 @@ fn update_blocks_at_finalized_height(
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
meta.state = State::Finalized(now.into());
|
||||
@@ -1014,7 +969,7 @@ fn update_blocks_at_finalized_height(
|
||||
);
|
||||
} else {
|
||||
meta.state = match meta.state {
|
||||
State::Finalized(_) => continue, // sanity.
|
||||
State::Finalized(_) => continue, // sanity.
|
||||
State::Unavailable(_) => continue, // sanity.
|
||||
State::Unfinalized(at, mut blocks) => {
|
||||
// Clear out everything at this height.
|
||||
@@ -1035,7 +990,7 @@ fn update_blocks_at_finalized_height(
|
||||
} else {
|
||||
State::Unfinalized(at, blocks)
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Update the meta entry.
|
||||
@@ -1053,20 +1008,22 @@ fn process_message(
|
||||
match msg {
|
||||
AvailabilityStoreMessage::QueryAvailableData(candidate, tx) => {
|
||||
let _ = tx.send(load_available_data(&subsystem.db, &subsystem.config, &candidate)?);
|
||||
}
|
||||
},
|
||||
AvailabilityStoreMessage::QueryDataAvailability(candidate, tx) => {
|
||||
let a = load_meta(&subsystem.db, &subsystem.config, &candidate)?.map_or(false, |m| m.data_available);
|
||||
let a = load_meta(&subsystem.db, &subsystem.config, &candidate)?
|
||||
.map_or(false, |m| m.data_available);
|
||||
let _ = tx.send(a);
|
||||
}
|
||||
},
|
||||
AvailabilityStoreMessage::QueryChunk(candidate, validator_index, tx) => {
|
||||
let _timer = subsystem.metrics.time_get_chunk();
|
||||
let _ = tx.send(load_chunk(&subsystem.db, &subsystem.config, &candidate, validator_index)?);
|
||||
}
|
||||
let _ =
|
||||
tx.send(load_chunk(&subsystem.db, &subsystem.config, &candidate, validator_index)?);
|
||||
},
|
||||
AvailabilityStoreMessage::QueryAllChunks(candidate, tx) => {
|
||||
match load_meta(&subsystem.db, &subsystem.config, &candidate)? {
|
||||
None => {
|
||||
let _ = tx.send(Vec::new());
|
||||
}
|
||||
},
|
||||
Some(meta) => {
|
||||
let mut chunks = Vec::new();
|
||||
|
||||
@@ -1086,64 +1043,61 @@ fn process_message(
|
||||
index,
|
||||
"No chunk found for set bit in meta"
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let _ = tx.send(chunks);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
AvailabilityStoreMessage::QueryChunkAvailability(candidate, validator_index, tx) => {
|
||||
let a = load_meta(&subsystem.db, &subsystem.config, &candidate)?
|
||||
.map_or(false, |m|
|
||||
*m.chunks_stored.get(validator_index.0 as usize).as_deref().unwrap_or(&false)
|
||||
);
|
||||
let a = load_meta(&subsystem.db, &subsystem.config, &candidate)?.map_or(false, |m| {
|
||||
*m.chunks_stored.get(validator_index.0 as usize).as_deref().unwrap_or(&false)
|
||||
});
|
||||
let _ = tx.send(a);
|
||||
}
|
||||
AvailabilityStoreMessage::StoreChunk {
|
||||
candidate_hash,
|
||||
chunk,
|
||||
tx,
|
||||
} => {
|
||||
},
|
||||
AvailabilityStoreMessage::StoreChunk { candidate_hash, chunk, tx } => {
|
||||
subsystem.metrics.on_chunks_received(1);
|
||||
let _timer = subsystem.metrics.time_store_chunk();
|
||||
|
||||
match store_chunk(&subsystem.db, &subsystem.config, candidate_hash, chunk) {
|
||||
Ok(true) => {
|
||||
let _ = tx.send(Ok(()));
|
||||
}
|
||||
},
|
||||
Ok(false) => {
|
||||
let _ = tx.send(Err(()));
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
let _ = tx.send(Err(()));
|
||||
return Err(e)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
AvailabilityStoreMessage::StoreAvailableData(candidate, _our_index, n_validators, available_data, tx) => {
|
||||
},
|
||||
AvailabilityStoreMessage::StoreAvailableData(
|
||||
candidate,
|
||||
_our_index,
|
||||
n_validators,
|
||||
available_data,
|
||||
tx,
|
||||
) => {
|
||||
subsystem.metrics.on_chunks_received(n_validators as _);
|
||||
|
||||
let _timer = subsystem.metrics.time_store_available_data();
|
||||
|
||||
let res = store_available_data(
|
||||
&subsystem,
|
||||
candidate,
|
||||
n_validators as _,
|
||||
available_data,
|
||||
);
|
||||
let res =
|
||||
store_available_data(&subsystem, candidate, n_validators as _, available_data);
|
||||
|
||||
match res {
|
||||
Ok(()) => {
|
||||
let _ = tx.send(Ok(()));
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
let _ = tx.send(Err(()));
|
||||
return Err(e)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -1170,7 +1124,7 @@ fn store_chunk(
|
||||
|
||||
write_chunk(&mut tx, config, &candidate_hash, chunk.index, &chunk);
|
||||
write_meta(&mut tx, config, &candidate_hash, &meta);
|
||||
}
|
||||
},
|
||||
None => return Ok(false), // out of bounds.
|
||||
}
|
||||
|
||||
@@ -1197,7 +1151,7 @@ fn store_available_data(
|
||||
let mut meta = match load_meta(&subsystem.db, &subsystem.config, &candidate_hash)? {
|
||||
Some(m) => {
|
||||
if m.data_available {
|
||||
return Ok(()); // already stored.
|
||||
return Ok(()) // already stored.
|
||||
}
|
||||
|
||||
m
|
||||
@@ -1214,20 +1168,19 @@ fn store_available_data(
|
||||
data_available: false,
|
||||
chunks_stored: BitVec::new(),
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let chunks = erasure::obtain_chunks_v1(n_validators, &available_data)?;
|
||||
let branches = erasure::branches(chunks.as_ref());
|
||||
|
||||
let erasure_chunks = chunks.iter()
|
||||
.zip(branches.map(|(proof, _)| proof))
|
||||
.enumerate()
|
||||
.map(|(index, (chunk, proof))| ErasureChunk {
|
||||
let erasure_chunks = chunks.iter().zip(branches.map(|(proof, _)| proof)).enumerate().map(
|
||||
|(index, (chunk, proof))| ErasureChunk {
|
||||
chunk: chunk.clone(),
|
||||
proof,
|
||||
index: ValidatorIndex(index as u32),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
for chunk in erasure_chunks {
|
||||
write_chunk(&mut tx, &subsystem.config, &candidate_hash, chunk.index, &chunk);
|
||||
@@ -1236,16 +1189,12 @@ fn store_available_data(
|
||||
meta.data_available = true;
|
||||
meta.chunks_stored = bitvec::bitvec![BitOrderLsb0, u8; 1; n_validators];
|
||||
|
||||
write_meta(&mut tx, &subsystem.config, &candidate_hash, &meta);
|
||||
write_meta(&mut tx, &subsystem.config, &candidate_hash, &meta);
|
||||
write_available_data(&mut tx, &subsystem.config, &candidate_hash, &available_data);
|
||||
|
||||
subsystem.db.write(tx)?;
|
||||
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
?candidate_hash,
|
||||
"Stored data and chunks",
|
||||
);
|
||||
tracing::debug!(target: LOG_TARGET, ?candidate_hash, "Stored data and chunks",);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1255,7 +1204,8 @@ fn prune_all(db: &Arc<dyn KeyValueDB>, config: &Config, clock: &dyn Clock) -> Re
|
||||
let (range_start, range_end) = pruning_range(now);
|
||||
|
||||
let mut tx = DBTransaction::new();
|
||||
let iter = db.iter_with_prefix(config.col_meta, &range_start[..])
|
||||
let iter = db
|
||||
.iter_with_prefix(config.col_meta, &range_start[..])
|
||||
.take_while(|(k, _)| &k[..] < &range_end[..]);
|
||||
|
||||
for (k, _v) in iter {
|
||||
@@ -1334,7 +1284,9 @@ impl Metrics {
|
||||
}
|
||||
|
||||
/// Provide a timer for `process_block_finalized` which observes on drop.
|
||||
fn time_process_block_finalized(&self) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
|
||||
fn time_process_block_finalized(
|
||||
&self,
|
||||
) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
|
||||
self.0.as_ref().map(|metrics| metrics.process_block_finalized.start_timer())
|
||||
}
|
||||
|
||||
@@ -1375,66 +1327,52 @@ impl metrics::Metrics for Metrics {
|
||||
registry,
|
||||
)?,
|
||||
pruning: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_av_store_pruning",
|
||||
"Time spent within `av_store::prune_all`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_av_store_pruning",
|
||||
"Time spent within `av_store::prune_all`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
process_block_finalized: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_av_store_process_block_finalized",
|
||||
"Time spent within `av_store::process_block_finalized`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_av_store_process_block_finalized",
|
||||
"Time spent within `av_store::process_block_finalized`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
block_activated: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_av_store_block_activated",
|
||||
"Time spent within `av_store::process_block_activated`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_av_store_block_activated",
|
||||
"Time spent within `av_store::process_block_activated`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
process_message: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_av_store_process_message",
|
||||
"Time spent within `av_store::process_message`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_av_store_process_message",
|
||||
"Time spent within `av_store::process_message`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
store_available_data: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_av_store_store_available_data",
|
||||
"Time spent within `av_store::store_available_data`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_av_store_store_available_data",
|
||||
"Time spent within `av_store::store_available_data`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
store_chunk: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_av_store_store_chunk",
|
||||
"Time spent within `av_store::store_chunk`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_av_store_store_chunk",
|
||||
"Time spent within `av_store::store_chunk`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
get_chunk: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_av_store_get_chunk",
|
||||
"Time spent fetching requested chunks.`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_av_store_get_chunk",
|
||||
"Time spent fetching requested chunks.`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
};
|
||||
|
||||
@@ -17,27 +17,23 @@
|
||||
use super::*;
|
||||
|
||||
use assert_matches::assert_matches;
|
||||
use futures::{
|
||||
future,
|
||||
channel::oneshot,
|
||||
executor,
|
||||
Future,
|
||||
};
|
||||
use futures::{channel::oneshot, executor, future, Future};
|
||||
|
||||
use polkadot_primitives::v1::{
|
||||
CandidateDescriptor, CandidateReceipt, HeadData,
|
||||
PersistedValidationData, Id as ParaId, CandidateHash, Header, ValidatorId,
|
||||
CoreIndex, GroupIndex,
|
||||
};
|
||||
use polkadot_node_primitives::{AvailableData, BlockData, PoV};
|
||||
use polkadot_node_subsystem_util::TimeoutExt;
|
||||
use polkadot_subsystem::{
|
||||
ActiveLeavesUpdate, errors::RuntimeApiError, jaeger, ActivatedLeaf,
|
||||
LeafStatus, messages::{AllMessages, RuntimeApiMessage, RuntimeApiRequest},
|
||||
};
|
||||
use polkadot_node_subsystem_test_helpers as test_helpers;
|
||||
use sp_keyring::Sr25519Keyring;
|
||||
use parking_lot::Mutex;
|
||||
use polkadot_node_primitives::{AvailableData, BlockData, PoV};
|
||||
use polkadot_node_subsystem_test_helpers as test_helpers;
|
||||
use polkadot_node_subsystem_util::TimeoutExt;
|
||||
use polkadot_primitives::v1::{
|
||||
CandidateDescriptor, CandidateHash, CandidateReceipt, CoreIndex, GroupIndex, HeadData, Header,
|
||||
Id as ParaId, PersistedValidationData, ValidatorId,
|
||||
};
|
||||
use polkadot_subsystem::{
|
||||
errors::RuntimeApiError,
|
||||
jaeger,
|
||||
messages::{AllMessages, RuntimeApiMessage, RuntimeApiRequest},
|
||||
ActivatedLeaf, ActiveLeavesUpdate, LeafStatus,
|
||||
};
|
||||
use sp_keyring::Sr25519Keyring;
|
||||
|
||||
mod columns {
|
||||
pub const DATA: u32 = 0;
|
||||
@@ -45,10 +41,7 @@ mod columns {
|
||||
pub const NUM_COLUMNS: u32 = 2;
|
||||
}
|
||||
|
||||
const TEST_CONFIG: Config = Config {
|
||||
col_data: columns::DATA,
|
||||
col_meta: columns::META,
|
||||
};
|
||||
const TEST_CONFIG: Config = Config { col_data: columns::DATA, col_meta: columns::META };
|
||||
|
||||
type VirtualOverseer = test_helpers::TestSubsystemContextHandle<AvailabilityStoreMessage>;
|
||||
|
||||
@@ -95,7 +88,6 @@ impl Clock for TestClock {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
struct TestState {
|
||||
persisted_validation_data: PersistedValidationData,
|
||||
@@ -126,34 +118,21 @@ impl Default for TestState {
|
||||
pruning_interval: Duration::from_millis(250),
|
||||
};
|
||||
|
||||
let clock = TestClock {
|
||||
inner: Arc::new(Mutex::new(Duration::from_secs(0))),
|
||||
};
|
||||
let clock = TestClock { inner: Arc::new(Mutex::new(Duration::from_secs(0))) };
|
||||
|
||||
Self {
|
||||
persisted_validation_data,
|
||||
pruning_config,
|
||||
clock,
|
||||
}
|
||||
Self { persisted_validation_data, pruning_config, clock }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn test_harness<T: Future<Output=VirtualOverseer>>(
|
||||
fn test_harness<T: Future<Output = VirtualOverseer>>(
|
||||
state: TestState,
|
||||
store: Arc<dyn KeyValueDB>,
|
||||
test: impl FnOnce(VirtualOverseer) -> T,
|
||||
) {
|
||||
let _ = env_logger::builder()
|
||||
.is_test(true)
|
||||
.filter(
|
||||
Some("polkadot_node_core_av_store"),
|
||||
log::LevelFilter::Trace,
|
||||
)
|
||||
.filter(
|
||||
Some(LOG_TARGET),
|
||||
log::LevelFilter::Trace,
|
||||
)
|
||||
.filter(Some("polkadot_node_core_av_store"), log::LevelFilter::Trace)
|
||||
.filter(Some(LOG_TARGET), log::LevelFilter::Trace)
|
||||
.try_init();
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
@@ -174,21 +153,18 @@ fn test_harness<T: Future<Output=VirtualOverseer>>(
|
||||
futures::pin_mut!(test_fut);
|
||||
futures::pin_mut!(subsystem);
|
||||
|
||||
executor::block_on(future::join(async move {
|
||||
let mut overseer = test_fut.await;
|
||||
overseer_signal(
|
||||
&mut overseer,
|
||||
OverseerSignal::Conclude,
|
||||
).await;
|
||||
}, subsystem));
|
||||
executor::block_on(future::join(
|
||||
async move {
|
||||
let mut overseer = test_fut.await;
|
||||
overseer_signal(&mut overseer, OverseerSignal::Conclude).await;
|
||||
},
|
||||
subsystem,
|
||||
));
|
||||
}
|
||||
|
||||
const TIMEOUT: Duration = Duration::from_millis(100);
|
||||
|
||||
async fn overseer_send(
|
||||
overseer: &mut VirtualOverseer,
|
||||
msg: AvailabilityStoreMessage,
|
||||
) {
|
||||
async fn overseer_send(overseer: &mut VirtualOverseer, msg: AvailabilityStoreMessage) {
|
||||
tracing::trace!(meg = ?msg, "sending message");
|
||||
overseer
|
||||
.send(FromOverseer::Communication { msg })
|
||||
@@ -197,9 +173,7 @@ async fn overseer_send(
|
||||
.expect(&format!("{:?} is more than enough for sending messages.", TIMEOUT));
|
||||
}
|
||||
|
||||
async fn overseer_recv(
|
||||
overseer: &mut VirtualOverseer,
|
||||
) -> AllMessages {
|
||||
async fn overseer_recv(overseer: &mut VirtualOverseer) -> AllMessages {
|
||||
let msg = overseer_recv_with_timeout(overseer, TIMEOUT)
|
||||
.await
|
||||
.expect(&format!("{:?} is more than enough to receive messages", TIMEOUT));
|
||||
@@ -214,16 +188,10 @@ async fn overseer_recv_with_timeout(
|
||||
timeout: Duration,
|
||||
) -> Option<AllMessages> {
|
||||
tracing::trace!("waiting for message...");
|
||||
overseer
|
||||
.recv()
|
||||
.timeout(timeout)
|
||||
.await
|
||||
overseer.recv().timeout(timeout).await
|
||||
}
|
||||
|
||||
async fn overseer_signal(
|
||||
overseer: &mut VirtualOverseer,
|
||||
signal: OverseerSignal,
|
||||
) {
|
||||
async fn overseer_signal(overseer: &mut VirtualOverseer, signal: OverseerSignal) {
|
||||
overseer
|
||||
.send(FromOverseer::Signal(signal))
|
||||
.timeout(TIMEOUT)
|
||||
@@ -261,7 +229,8 @@ fn runtime_api_error_does_not_stop_the_subsystem() {
|
||||
status: LeafStatus::Fresh,
|
||||
span: Arc::new(jaeger::Span::Disabled),
|
||||
})),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
let header = Header {
|
||||
parent_hash: Hash::zero(),
|
||||
@@ -298,11 +267,7 @@ fn runtime_api_error_does_not_stop_the_subsystem() {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let candidate_hash = CandidateHash(Hash::repeat_byte(33));
|
||||
let validator_index = ValidatorIndex(5);
|
||||
let query_chunk = AvailabilityStoreMessage::QueryChunk(
|
||||
candidate_hash,
|
||||
validator_index,
|
||||
tx,
|
||||
);
|
||||
let query_chunk = AvailabilityStoreMessage::QueryChunk(candidate_hash, validator_index, tx);
|
||||
|
||||
overseer_send(&mut virtual_overseer, query_chunk.into()).await;
|
||||
|
||||
@@ -328,30 +293,28 @@ fn store_chunk_works() {
|
||||
// Ensure an entry already exists. In reality this would come from watching
|
||||
// chain events.
|
||||
with_tx(&store, |tx| {
|
||||
super::write_meta(tx, &TEST_CONFIG, &candidate_hash, &CandidateMeta {
|
||||
data_available: false,
|
||||
chunks_stored: bitvec::bitvec![BitOrderLsb0, u8; 0; n_validators],
|
||||
state: State::Unavailable(BETimestamp(0)),
|
||||
});
|
||||
super::write_meta(
|
||||
tx,
|
||||
&TEST_CONFIG,
|
||||
&candidate_hash,
|
||||
&CandidateMeta {
|
||||
data_available: false,
|
||||
chunks_stored: bitvec::bitvec![BitOrderLsb0, u8; 0; n_validators],
|
||||
state: State::Unavailable(BETimestamp(0)),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
let chunk_msg = AvailabilityStoreMessage::StoreChunk {
|
||||
candidate_hash,
|
||||
chunk: chunk.clone(),
|
||||
tx,
|
||||
};
|
||||
let chunk_msg =
|
||||
AvailabilityStoreMessage::StoreChunk { candidate_hash, chunk: chunk.clone(), tx };
|
||||
|
||||
overseer_send(&mut virtual_overseer, chunk_msg.into()).await;
|
||||
assert_eq!(rx.await.unwrap(), Ok(()));
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let query_chunk = AvailabilityStoreMessage::QueryChunk(
|
||||
candidate_hash,
|
||||
validator_index,
|
||||
tx,
|
||||
);
|
||||
let query_chunk = AvailabilityStoreMessage::QueryChunk(candidate_hash, validator_index, tx);
|
||||
|
||||
overseer_send(&mut virtual_overseer, query_chunk.into()).await;
|
||||
|
||||
@@ -360,7 +323,6 @@ fn store_chunk_works() {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn store_chunk_does_nothing_if_no_entry_already() {
|
||||
let store = Arc::new(kvdb_memorydb::create(columns::NUM_COLUMNS));
|
||||
@@ -376,21 +338,14 @@ fn store_chunk_does_nothing_if_no_entry_already() {
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
let chunk_msg = AvailabilityStoreMessage::StoreChunk {
|
||||
candidate_hash,
|
||||
chunk: chunk.clone(),
|
||||
tx,
|
||||
};
|
||||
let chunk_msg =
|
||||
AvailabilityStoreMessage::StoreChunk { candidate_hash, chunk: chunk.clone(), tx };
|
||||
|
||||
overseer_send(&mut virtual_overseer, chunk_msg.into()).await;
|
||||
assert_eq!(rx.await.unwrap(), Err(()));
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let query_chunk = AvailabilityStoreMessage::QueryChunk(
|
||||
candidate_hash,
|
||||
validator_index,
|
||||
tx,
|
||||
);
|
||||
let query_chunk = AvailabilityStoreMessage::QueryChunk(candidate_hash, validator_index, tx);
|
||||
|
||||
overseer_send(&mut virtual_overseer, query_chunk.into()).await;
|
||||
|
||||
@@ -410,23 +365,25 @@ fn query_chunk_checks_meta() {
|
||||
// Ensure an entry already exists. In reality this would come from watching
|
||||
// chain events.
|
||||
with_tx(&store, |tx| {
|
||||
super::write_meta(tx, &TEST_CONFIG, &candidate_hash, &CandidateMeta {
|
||||
data_available: false,
|
||||
chunks_stored: {
|
||||
let mut v = bitvec::bitvec![BitOrderLsb0, u8; 0; n_validators];
|
||||
v.set(validator_index.0 as usize, true);
|
||||
v
|
||||
super::write_meta(
|
||||
tx,
|
||||
&TEST_CONFIG,
|
||||
&candidate_hash,
|
||||
&CandidateMeta {
|
||||
data_available: false,
|
||||
chunks_stored: {
|
||||
let mut v = bitvec::bitvec![BitOrderLsb0, u8; 0; n_validators];
|
||||
v.set(validator_index.0 as usize, true);
|
||||
v
|
||||
},
|
||||
state: State::Unavailable(BETimestamp(0)),
|
||||
},
|
||||
state: State::Unavailable(BETimestamp(0)),
|
||||
});
|
||||
);
|
||||
});
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let query_chunk = AvailabilityStoreMessage::QueryChunkAvailability(
|
||||
candidate_hash,
|
||||
validator_index,
|
||||
tx,
|
||||
);
|
||||
let query_chunk =
|
||||
AvailabilityStoreMessage::QueryChunkAvailability(candidate_hash, validator_index, tx);
|
||||
|
||||
overseer_send(&mut virtual_overseer, query_chunk.into()).await;
|
||||
assert!(rx.await.unwrap());
|
||||
@@ -453,16 +410,13 @@ fn store_block_works() {
|
||||
let validator_index = ValidatorIndex(5);
|
||||
let n_validators = 10;
|
||||
|
||||
let pov = PoV {
|
||||
block_data: BlockData(vec![4, 5, 6]),
|
||||
};
|
||||
let pov = PoV { block_data: BlockData(vec![4, 5, 6]) };
|
||||
|
||||
let available_data = AvailableData {
|
||||
pov: Arc::new(pov),
|
||||
validation_data: test_state.persisted_validation_data.clone(),
|
||||
};
|
||||
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let block_msg = AvailabilityStoreMessage::StoreAvailableData(
|
||||
candidate_hash,
|
||||
@@ -472,24 +426,23 @@ fn store_block_works() {
|
||||
tx,
|
||||
);
|
||||
|
||||
virtual_overseer.send(FromOverseer::Communication{ msg: block_msg }).await;
|
||||
virtual_overseer.send(FromOverseer::Communication { msg: block_msg }).await;
|
||||
assert_eq!(rx.await.unwrap(), Ok(()));
|
||||
|
||||
let pov = query_available_data(&mut virtual_overseer, candidate_hash).await.unwrap();
|
||||
assert_eq!(pov, available_data);
|
||||
|
||||
let chunk = query_chunk(&mut virtual_overseer, candidate_hash, validator_index).await.unwrap();
|
||||
let chunk = query_chunk(&mut virtual_overseer, candidate_hash, validator_index)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let chunks = erasure::obtain_chunks_v1(10, &available_data).unwrap();
|
||||
|
||||
let mut branches = erasure::branches(chunks.as_ref());
|
||||
|
||||
let branch = branches.nth(5).unwrap();
|
||||
let expected_chunk = ErasureChunk {
|
||||
chunk: branch.1.to_vec(),
|
||||
index: ValidatorIndex(5),
|
||||
proof: branch.0,
|
||||
};
|
||||
let expected_chunk =
|
||||
ErasureChunk { chunk: branch.1.to_vec(), index: ValidatorIndex(5), proof: branch.0 };
|
||||
|
||||
assert_eq!(chunk, expected_chunk);
|
||||
virtual_overseer
|
||||
@@ -505,16 +458,15 @@ fn store_pov_and_query_chunk_works() {
|
||||
let candidate_hash = CandidateHash(Hash::repeat_byte(1));
|
||||
let n_validators = 10;
|
||||
|
||||
let pov = PoV {
|
||||
block_data: BlockData(vec![4, 5, 6]),
|
||||
};
|
||||
let pov = PoV { block_data: BlockData(vec![4, 5, 6]) };
|
||||
|
||||
let available_data = AvailableData {
|
||||
pov: Arc::new(pov),
|
||||
validation_data: test_state.persisted_validation_data.clone(),
|
||||
};
|
||||
|
||||
let chunks_expected = erasure::obtain_chunks_v1(n_validators as _, &available_data).unwrap();
|
||||
let chunks_expected =
|
||||
erasure::obtain_chunks_v1(n_validators as _, &available_data).unwrap();
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let block_msg = AvailabilityStoreMessage::StoreAvailableData(
|
||||
@@ -525,12 +477,14 @@ fn store_pov_and_query_chunk_works() {
|
||||
tx,
|
||||
);
|
||||
|
||||
virtual_overseer.send(FromOverseer::Communication{ msg: block_msg }).await;
|
||||
virtual_overseer.send(FromOverseer::Communication { msg: block_msg }).await;
|
||||
|
||||
assert_eq!(rx.await.unwrap(), Ok(()));
|
||||
|
||||
for i in 0..n_validators {
|
||||
let chunk = query_chunk(&mut virtual_overseer, candidate_hash, ValidatorIndex(i as _)).await.unwrap();
|
||||
let chunk = query_chunk(&mut virtual_overseer, candidate_hash, ValidatorIndex(i as _))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(chunk.chunk, chunks_expected[i as usize]);
|
||||
}
|
||||
@@ -553,9 +507,7 @@ fn query_all_chunks_works() {
|
||||
|
||||
let n_validators = 10;
|
||||
|
||||
let pov = PoV {
|
||||
block_data: BlockData(vec![4, 5, 6]),
|
||||
};
|
||||
let pov = PoV { block_data: BlockData(vec![4, 5, 6]) };
|
||||
|
||||
let available_data = AvailableData {
|
||||
pov: Arc::new(pov),
|
||||
@@ -578,11 +530,16 @@ fn query_all_chunks_works() {
|
||||
|
||||
{
|
||||
with_tx(&store, |tx| {
|
||||
super::write_meta(tx, &TEST_CONFIG, &candidate_hash_2, &CandidateMeta {
|
||||
data_available: false,
|
||||
chunks_stored: bitvec::bitvec![BitOrderLsb0, u8; 0; n_validators as _],
|
||||
state: State::Unavailable(BETimestamp(0)),
|
||||
});
|
||||
super::write_meta(
|
||||
tx,
|
||||
&TEST_CONFIG,
|
||||
&candidate_hash_2,
|
||||
&CandidateMeta {
|
||||
data_available: false,
|
||||
chunks_stored: bitvec::bitvec![BitOrderLsb0, u8; 0; n_validators as _],
|
||||
state: State::Unavailable(BETimestamp(0)),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
let chunk = ErasureChunk {
|
||||
@@ -598,7 +555,9 @@ fn query_all_chunks_works() {
|
||||
tx,
|
||||
};
|
||||
|
||||
virtual_overseer.send(FromOverseer::Communication { msg: store_chunk_msg }).await;
|
||||
virtual_overseer
|
||||
.send(FromOverseer::Communication { msg: store_chunk_msg })
|
||||
.await;
|
||||
assert_eq!(rx.await.unwrap(), Ok(()));
|
||||
}
|
||||
|
||||
@@ -638,9 +597,7 @@ fn stored_but_not_included_data_is_pruned() {
|
||||
let candidate_hash = CandidateHash(Hash::repeat_byte(1));
|
||||
let n_validators = 10;
|
||||
|
||||
let pov = PoV {
|
||||
block_data: BlockData(vec![4, 5, 6]),
|
||||
};
|
||||
let pov = PoV { block_data: BlockData(vec![4, 5, 6]) };
|
||||
|
||||
let available_data = AvailableData {
|
||||
pov: Arc::new(pov),
|
||||
@@ -656,7 +613,7 @@ fn stored_but_not_included_data_is_pruned() {
|
||||
tx,
|
||||
);
|
||||
|
||||
virtual_overseer.send(FromOverseer::Communication{ msg: block_msg }).await;
|
||||
virtual_overseer.send(FromOverseer::Communication { msg: block_msg }).await;
|
||||
|
||||
rx.await.unwrap().unwrap();
|
||||
|
||||
@@ -684,16 +641,11 @@ fn stored_data_kept_until_finalized() {
|
||||
test_harness(test_state.clone(), store.clone(), |mut virtual_overseer| async move {
|
||||
let n_validators = 10;
|
||||
|
||||
let pov = PoV {
|
||||
block_data: BlockData(vec![4, 5, 6]),
|
||||
};
|
||||
let pov = PoV { block_data: BlockData(vec![4, 5, 6]) };
|
||||
|
||||
let pov_hash = pov.hash();
|
||||
|
||||
let candidate = TestCandidateBuilder {
|
||||
pov_hash,
|
||||
..Default::default()
|
||||
}.build();
|
||||
let candidate = TestCandidateBuilder { pov_hash, ..Default::default() }.build();
|
||||
|
||||
let candidate_hash = candidate.hash();
|
||||
|
||||
@@ -714,7 +666,7 @@ fn stored_data_kept_until_finalized() {
|
||||
tx,
|
||||
);
|
||||
|
||||
virtual_overseer.send(FromOverseer::Communication{ msg: block_msg }).await;
|
||||
virtual_overseer.send(FromOverseer::Communication { msg: block_msg }).await;
|
||||
|
||||
rx.await.unwrap().unwrap();
|
||||
|
||||
@@ -730,7 +682,8 @@ fn stored_data_kept_until_finalized() {
|
||||
block_number,
|
||||
vec![candidate_included(candidate)],
|
||||
(0..n_validators).map(|_| Sr25519Keyring::Alice.public().into()).collect(),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
// Wait until unavailable data would definitely be pruned.
|
||||
test_state.clock.inc(test_state.pruning_config.keep_unavailable_for * 10);
|
||||
@@ -742,14 +695,13 @@ fn stored_data_kept_until_finalized() {
|
||||
available_data,
|
||||
);
|
||||
|
||||
assert!(
|
||||
has_all_chunks(&mut virtual_overseer, candidate_hash, n_validators, true).await
|
||||
);
|
||||
assert!(has_all_chunks(&mut virtual_overseer, candidate_hash, n_validators, true).await);
|
||||
|
||||
overseer_signal(
|
||||
&mut virtual_overseer,
|
||||
OverseerSignal::BlockFinalized(new_leaf, block_number)
|
||||
).await;
|
||||
OverseerSignal::BlockFinalized(new_leaf, block_number),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Wait until unavailable data would definitely be pruned.
|
||||
test_state.clock.inc(test_state.pruning_config.keep_finalized_for / 2);
|
||||
@@ -761,22 +713,16 @@ fn stored_data_kept_until_finalized() {
|
||||
available_data,
|
||||
);
|
||||
|
||||
assert!(
|
||||
has_all_chunks(&mut virtual_overseer, candidate_hash, n_validators, true).await
|
||||
);
|
||||
assert!(has_all_chunks(&mut virtual_overseer, candidate_hash, n_validators, true).await);
|
||||
|
||||
// Wait until it definitely should be gone.
|
||||
test_state.clock.inc(test_state.pruning_config.keep_finalized_for);
|
||||
test_state.wait_for_pruning().await;
|
||||
|
||||
// At this point data should be gone from the store.
|
||||
assert!(
|
||||
query_available_data(&mut virtual_overseer, candidate_hash).await.is_none(),
|
||||
);
|
||||
assert!(query_available_data(&mut virtual_overseer, candidate_hash).await.is_none(),);
|
||||
|
||||
assert!(
|
||||
has_all_chunks(&mut virtual_overseer, candidate_hash, n_validators, false).await
|
||||
);
|
||||
assert!(has_all_chunks(&mut virtual_overseer, candidate_hash, n_validators, false).await);
|
||||
virtual_overseer
|
||||
});
|
||||
}
|
||||
@@ -787,10 +733,8 @@ fn we_dont_miss_anything_if_import_notifications_are_missed() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
test_harness(test_state.clone(), store.clone(), |mut virtual_overseer| async move {
|
||||
overseer_signal(
|
||||
&mut virtual_overseer,
|
||||
OverseerSignal::BlockFinalized(Hash::zero(), 1)
|
||||
).await;
|
||||
overseer_signal(&mut virtual_overseer, OverseerSignal::BlockFinalized(Hash::zero(), 1))
|
||||
.await;
|
||||
|
||||
let header = Header {
|
||||
parent_hash: Hash::repeat_byte(3),
|
||||
@@ -809,7 +753,8 @@ fn we_dont_miss_anything_if_import_notifications_are_missed() {
|
||||
status: LeafStatus::Fresh,
|
||||
span: Arc::new(jaeger::Span::Disabled),
|
||||
})),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_matches!(
|
||||
overseer_recv(&mut virtual_overseer).await,
|
||||
@@ -915,33 +860,26 @@ fn forkfullness_works() {
|
||||
let n_validators = 10;
|
||||
let block_number_1 = 5;
|
||||
let block_number_2 = 5;
|
||||
let validators: Vec<_> = (0..n_validators).map(|_| Sr25519Keyring::Alice.public().into()).collect();
|
||||
let validators: Vec<_> =
|
||||
(0..n_validators).map(|_| Sr25519Keyring::Alice.public().into()).collect();
|
||||
let parent_1 = Hash::repeat_byte(3);
|
||||
let parent_2 = Hash::repeat_byte(4);
|
||||
|
||||
let pov_1 = PoV {
|
||||
block_data: BlockData(vec![1, 2, 3]),
|
||||
};
|
||||
let pov_1 = PoV { block_data: BlockData(vec![1, 2, 3]) };
|
||||
|
||||
let pov_1_hash = pov_1.hash();
|
||||
|
||||
let pov_2 = PoV {
|
||||
block_data: BlockData(vec![4, 5, 6]),
|
||||
};
|
||||
let pov_2 = PoV { block_data: BlockData(vec![4, 5, 6]) };
|
||||
|
||||
let pov_2_hash = pov_2.hash();
|
||||
|
||||
let candidate_1 = TestCandidateBuilder {
|
||||
pov_hash: pov_1_hash,
|
||||
..Default::default()
|
||||
}.build();
|
||||
let candidate_1 =
|
||||
TestCandidateBuilder { pov_hash: pov_1_hash, ..Default::default() }.build();
|
||||
|
||||
let candidate_1_hash = candidate_1.hash();
|
||||
|
||||
let candidate_2 = TestCandidateBuilder {
|
||||
pov_hash: pov_2_hash,
|
||||
..Default::default()
|
||||
}.build();
|
||||
let candidate_2 =
|
||||
TestCandidateBuilder { pov_hash: pov_2_hash, ..Default::default() }.build();
|
||||
|
||||
let candidate_2_hash = candidate_2.hash();
|
||||
|
||||
@@ -964,7 +902,7 @@ fn forkfullness_works() {
|
||||
tx,
|
||||
);
|
||||
|
||||
virtual_overseer.send(FromOverseer::Communication{ msg }).await;
|
||||
virtual_overseer.send(FromOverseer::Communication { msg }).await;
|
||||
|
||||
rx.await.unwrap().unwrap();
|
||||
|
||||
@@ -977,7 +915,7 @@ fn forkfullness_works() {
|
||||
tx,
|
||||
);
|
||||
|
||||
virtual_overseer.send(FromOverseer::Communication{ msg }).await;
|
||||
virtual_overseer.send(FromOverseer::Communication { msg }).await;
|
||||
|
||||
rx.await.unwrap().unwrap();
|
||||
|
||||
@@ -997,7 +935,8 @@ fn forkfullness_works() {
|
||||
block_number_1,
|
||||
vec![candidate_included(candidate_1)],
|
||||
validators.clone(),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
let _new_leaf_2 = import_leaf(
|
||||
&mut virtual_overseer,
|
||||
@@ -1005,12 +944,14 @@ fn forkfullness_works() {
|
||||
block_number_2,
|
||||
vec![candidate_included(candidate_2)],
|
||||
validators.clone(),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
overseer_signal(
|
||||
&mut virtual_overseer,
|
||||
OverseerSignal::BlockFinalized(new_leaf_1, block_number_1)
|
||||
).await;
|
||||
OverseerSignal::BlockFinalized(new_leaf_1, block_number_1),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Data of both candidates should be still present in the DB.
|
||||
assert_eq!(
|
||||
@@ -1023,13 +964,9 @@ fn forkfullness_works() {
|
||||
available_data_2,
|
||||
);
|
||||
|
||||
assert!(
|
||||
has_all_chunks(&mut virtual_overseer, candidate_1_hash, n_validators, true).await,
|
||||
);
|
||||
assert!(has_all_chunks(&mut virtual_overseer, candidate_1_hash, n_validators, true).await,);
|
||||
|
||||
assert!(
|
||||
has_all_chunks(&mut virtual_overseer, candidate_2_hash, n_validators, true).await,
|
||||
);
|
||||
assert!(has_all_chunks(&mut virtual_overseer, candidate_2_hash, n_validators, true).await,);
|
||||
|
||||
// Candidate 2 should now be considered unavailable and will be pruned.
|
||||
test_state.clock.inc(test_state.pruning_config.keep_unavailable_for);
|
||||
@@ -1040,38 +977,24 @@ fn forkfullness_works() {
|
||||
available_data_1,
|
||||
);
|
||||
|
||||
assert!(
|
||||
query_available_data(&mut virtual_overseer, candidate_2_hash).await.is_none(),
|
||||
);
|
||||
assert!(query_available_data(&mut virtual_overseer, candidate_2_hash).await.is_none(),);
|
||||
|
||||
assert!(
|
||||
has_all_chunks(&mut virtual_overseer, candidate_1_hash, n_validators, true).await,
|
||||
);
|
||||
assert!(has_all_chunks(&mut virtual_overseer, candidate_1_hash, n_validators, true).await,);
|
||||
|
||||
assert!(
|
||||
has_all_chunks(&mut virtual_overseer, candidate_2_hash, n_validators, false).await,
|
||||
);
|
||||
assert!(has_all_chunks(&mut virtual_overseer, candidate_2_hash, n_validators, false).await,);
|
||||
|
||||
// Wait for longer than finalized blocks should be kept for
|
||||
test_state.clock.inc(test_state.pruning_config.keep_finalized_for);
|
||||
test_state.wait_for_pruning().await;
|
||||
|
||||
// Everything should be pruned now.
|
||||
assert!(
|
||||
query_available_data(&mut virtual_overseer, candidate_1_hash).await.is_none(),
|
||||
);
|
||||
assert!(query_available_data(&mut virtual_overseer, candidate_1_hash).await.is_none(),);
|
||||
|
||||
assert!(
|
||||
query_available_data(&mut virtual_overseer, candidate_2_hash).await.is_none(),
|
||||
);
|
||||
assert!(query_available_data(&mut virtual_overseer, candidate_2_hash).await.is_none(),);
|
||||
|
||||
assert!(
|
||||
has_all_chunks(&mut virtual_overseer, candidate_1_hash, n_validators, false).await,
|
||||
);
|
||||
assert!(has_all_chunks(&mut virtual_overseer, candidate_1_hash, n_validators, false).await,);
|
||||
|
||||
assert!(
|
||||
has_all_chunks(&mut virtual_overseer, candidate_2_hash, n_validators, false).await,
|
||||
);
|
||||
assert!(has_all_chunks(&mut virtual_overseer, candidate_2_hash, n_validators, false).await,);
|
||||
virtual_overseer
|
||||
});
|
||||
}
|
||||
@@ -1083,7 +1006,7 @@ async fn query_available_data(
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
let query = AvailabilityStoreMessage::QueryAvailableData(candidate_hash, tx);
|
||||
virtual_overseer.send(FromOverseer::Communication{ msg: query }).await;
|
||||
virtual_overseer.send(FromOverseer::Communication { msg: query }).await;
|
||||
|
||||
rx.await.unwrap()
|
||||
}
|
||||
@@ -1096,7 +1019,7 @@ async fn query_chunk(
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
let query = AvailabilityStoreMessage::QueryChunk(candidate_hash, index, tx);
|
||||
virtual_overseer.send(FromOverseer::Communication{ msg: query }).await;
|
||||
virtual_overseer.send(FromOverseer::Communication { msg: query }).await;
|
||||
|
||||
rx.await.unwrap()
|
||||
}
|
||||
@@ -1108,7 +1031,9 @@ async fn has_all_chunks(
|
||||
expect_present: bool,
|
||||
) -> bool {
|
||||
for i in 0..n_validators {
|
||||
if query_chunk(virtual_overseer, candidate_hash, ValidatorIndex(i)).await.is_some() != expect_present {
|
||||
if query_chunk(virtual_overseer, candidate_hash, ValidatorIndex(i)).await.is_some() !=
|
||||
expect_present
|
||||
{
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -1139,7 +1064,8 @@ async fn import_leaf(
|
||||
status: LeafStatus::Fresh,
|
||||
span: Arc::new(jaeger::Span::Disabled),
|
||||
})),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_matches!(
|
||||
overseer_recv(virtual_overseer).await,
|
||||
@@ -1163,7 +1089,6 @@ async fn import_leaf(
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
assert_matches!(
|
||||
overseer_recv(virtual_overseer).await,
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
|
||||
@@ -18,55 +18,50 @@
|
||||
|
||||
#![deny(unused_crate_dependencies)]
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use bitvec::vec::BitVec;
|
||||
use futures::{channel::{mpsc, oneshot}, Future, FutureExt, SinkExt, StreamExt};
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
Future, FutureExt, SinkExt, StreamExt,
|
||||
};
|
||||
|
||||
use sp_keystore::SyncCryptoStorePtr;
|
||||
use polkadot_primitives::v1::{
|
||||
BackedCandidate, CandidateCommitments, CandidateDescriptor, CandidateHash,
|
||||
CandidateReceipt, CollatorId, CommittedCandidateReceipt, CoreIndex, CoreState, Hash, Id as ParaId,
|
||||
SigningContext, ValidatorId, ValidatorIndex, ValidatorSignature, ValidityAttestation,
|
||||
SessionIndex,
|
||||
};
|
||||
use polkadot_node_primitives::{
|
||||
Statement, SignedFullStatement, ValidationResult, PoV, AvailableData, SignedDisputeStatement,
|
||||
};
|
||||
use polkadot_subsystem::{
|
||||
PerLeafSpan, Stage, SubsystemSender,
|
||||
jaeger,
|
||||
overseer,
|
||||
messages::{
|
||||
AllMessages, AvailabilityDistributionMessage, AvailabilityStoreMessage,
|
||||
CandidateBackingMessage, CandidateValidationMessage, CollatorProtocolMessage,
|
||||
ProvisionableData, ProvisionerMessage, RuntimeApiRequest,
|
||||
StatementDistributionMessage, ValidationFailed, DisputeCoordinatorMessage,
|
||||
ImportStatementsResult,
|
||||
}
|
||||
AvailableData, PoV, SignedDisputeStatement, SignedFullStatement, Statement, ValidationResult,
|
||||
};
|
||||
use polkadot_node_subsystem_util::{
|
||||
self as util,
|
||||
request_session_index_for_child,
|
||||
request_validator_groups,
|
||||
request_validators,
|
||||
request_from_runtime,
|
||||
Validator,
|
||||
FromJobCommand,
|
||||
JobSender,
|
||||
metrics::{self, prometheus},
|
||||
request_from_runtime, request_session_index_for_child, request_validator_groups,
|
||||
request_validators, FromJobCommand, JobSender, Validator,
|
||||
};
|
||||
use polkadot_primitives::v1::{
|
||||
BackedCandidate, CandidateCommitments, CandidateDescriptor, CandidateHash, CandidateReceipt,
|
||||
CollatorId, CommittedCandidateReceipt, CoreIndex, CoreState, Hash, Id as ParaId, SessionIndex,
|
||||
SigningContext, ValidatorId, ValidatorIndex, ValidatorSignature, ValidityAttestation,
|
||||
};
|
||||
use polkadot_subsystem::{
|
||||
jaeger,
|
||||
messages::{
|
||||
AllMessages, AvailabilityDistributionMessage, AvailabilityStoreMessage,
|
||||
CandidateBackingMessage, CandidateValidationMessage, CollatorProtocolMessage,
|
||||
DisputeCoordinatorMessage, ImportStatementsResult, ProvisionableData, ProvisionerMessage,
|
||||
RuntimeApiRequest, StatementDistributionMessage, ValidationFailed,
|
||||
},
|
||||
overseer, PerLeafSpan, Stage, SubsystemSender,
|
||||
};
|
||||
use sp_keystore::SyncCryptoStorePtr;
|
||||
use statement_table::{
|
||||
generic::AttestedCandidate as TableAttestedCandidate,
|
||||
Context as TableContextTrait,
|
||||
Table,
|
||||
v1::{
|
||||
SignedStatement as TableSignedStatement,
|
||||
Statement as TableStatement,
|
||||
SignedStatement as TableSignedStatement, Statement as TableStatement,
|
||||
Summary as TableSummary,
|
||||
},
|
||||
Context as TableContextTrait, Table,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -127,12 +122,9 @@ impl std::fmt::Debug for ValidatedCandidateCommand {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let candidate_hash = self.candidate_hash();
|
||||
match *self {
|
||||
ValidatedCandidateCommand::Second(_) =>
|
||||
write!(f, "Second({})", candidate_hash),
|
||||
ValidatedCandidateCommand::Attest(_) =>
|
||||
write!(f, "Attest({})", candidate_hash),
|
||||
ValidatedCandidateCommand::AttestNoPoV(_) =>
|
||||
write!(f, "Attest({})", candidate_hash),
|
||||
ValidatedCandidateCommand::Second(_) => write!(f, "Second({})", candidate_hash),
|
||||
ValidatedCandidateCommand::Attest(_) => write!(f, "Attest({})", candidate_hash),
|
||||
ValidatedCandidateCommand::AttestNoPoV(_) => write!(f, "Attest({})", candidate_hash),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -224,7 +216,9 @@ impl TableContextTrait for TableContext {
|
||||
}
|
||||
|
||||
fn is_member_of(&self, authority: &ValidatorIndex, group: &ParaId) -> bool {
|
||||
self.groups.get(group).map_or(false, |g| g.iter().position(|a| a == authority).is_some())
|
||||
self.groups
|
||||
.get(group)
|
||||
.map_or(false, |g| g.iter().position(|a| a == authority).is_some())
|
||||
}
|
||||
|
||||
fn requisite_votes(&self, group: &ParaId) -> usize {
|
||||
@@ -260,10 +254,8 @@ fn table_attested_to_backed(
|
||||
) -> Option<BackedCandidate> {
|
||||
let TableAttestedCandidate { candidate, validity_votes, group_id: para_id } = attested;
|
||||
|
||||
let (ids, validity_votes): (Vec<_>, Vec<ValidityAttestation>) = validity_votes
|
||||
.into_iter()
|
||||
.map(|(id, vote)| (id, vote.into()))
|
||||
.unzip();
|
||||
let (ids, validity_votes): (Vec<_>, Vec<ValidityAttestation>) =
|
||||
validity_votes.into_iter().map(|(id, vote)| (id, vote.into())).unzip();
|
||||
|
||||
let group = table_context.groups.get(¶_id)?;
|
||||
|
||||
@@ -285,14 +277,15 @@ fn table_attested_to_backed(
|
||||
"Logic error: Validity vote from table does not correspond to group",
|
||||
);
|
||||
|
||||
return None;
|
||||
return None
|
||||
}
|
||||
}
|
||||
vote_positions.sort_by_key(|(_orig, pos_in_group)| *pos_in_group);
|
||||
|
||||
Some(BackedCandidate {
|
||||
candidate,
|
||||
validity_votes: vote_positions.into_iter()
|
||||
validity_votes: vote_positions
|
||||
.into_iter()
|
||||
.map(|(pos_in_votes, _pos_in_group)| validity_votes[pos_in_votes].clone())
|
||||
.collect(),
|
||||
validator_indices,
|
||||
@@ -307,13 +300,15 @@ async fn store_available_data(
|
||||
available_data: AvailableData,
|
||||
) -> Result<(), Error> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
sender.send_message(AvailabilityStoreMessage::StoreAvailableData(
|
||||
candidate_hash,
|
||||
id,
|
||||
n_validators,
|
||||
available_data,
|
||||
tx,
|
||||
)).await;
|
||||
sender
|
||||
.send_message(AvailabilityStoreMessage::StoreAvailableData(
|
||||
candidate_hash,
|
||||
id,
|
||||
n_validators,
|
||||
available_data,
|
||||
tx,
|
||||
))
|
||||
.await;
|
||||
|
||||
let _ = rx.await.map_err(Error::StoreAvailableData)?;
|
||||
|
||||
@@ -334,33 +329,23 @@ async fn make_pov_available(
|
||||
expected_erasure_root: Hash,
|
||||
span: Option<&jaeger::Span>,
|
||||
) -> Result<Result<(), InvalidErasureRoot>, Error> {
|
||||
let available_data = AvailableData {
|
||||
pov,
|
||||
validation_data,
|
||||
};
|
||||
let available_data = AvailableData { pov, validation_data };
|
||||
|
||||
{
|
||||
let _span = span.as_ref().map(|s| {
|
||||
s.child("erasure-coding").with_candidate(candidate_hash)
|
||||
});
|
||||
let _span = span.as_ref().map(|s| s.child("erasure-coding").with_candidate(candidate_hash));
|
||||
|
||||
let chunks = erasure_coding::obtain_chunks_v1(
|
||||
n_validators,
|
||||
&available_data,
|
||||
)?;
|
||||
let chunks = erasure_coding::obtain_chunks_v1(n_validators, &available_data)?;
|
||||
|
||||
let branches = erasure_coding::branches(chunks.as_ref());
|
||||
let erasure_root = branches.root();
|
||||
|
||||
if erasure_root != expected_erasure_root {
|
||||
return Ok(Err(InvalidErasureRoot));
|
||||
return Ok(Err(InvalidErasureRoot))
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let _span = span.as_ref().map(|s|
|
||||
s.child("store-data").with_candidate(candidate_hash)
|
||||
);
|
||||
let _span = span.as_ref().map(|s| s.child("store-data").with_candidate(candidate_hash));
|
||||
|
||||
store_available_data(
|
||||
sender,
|
||||
@@ -368,7 +353,8 @@ async fn make_pov_available(
|
||||
n_validators as u32,
|
||||
candidate_hash,
|
||||
available_data,
|
||||
).await?;
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(Ok(()))
|
||||
@@ -381,15 +367,16 @@ async fn request_pov(
|
||||
candidate_hash: CandidateHash,
|
||||
pov_hash: Hash,
|
||||
) -> Result<Arc<PoV>, Error> {
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
sender.send_message(AvailabilityDistributionMessage::FetchPoV {
|
||||
relay_parent,
|
||||
from_validator,
|
||||
candidate_hash,
|
||||
pov_hash,
|
||||
tx,
|
||||
}).await;
|
||||
sender
|
||||
.send_message(AvailabilityDistributionMessage::FetchPoV {
|
||||
relay_parent,
|
||||
from_validator,
|
||||
candidate_hash,
|
||||
pov_hash,
|
||||
tx,
|
||||
})
|
||||
.await;
|
||||
|
||||
let pov = rx.await.map_err(|_| Error::FetchPoV)?;
|
||||
Ok(Arc::new(pov))
|
||||
@@ -402,13 +389,9 @@ async fn request_candidate_validation(
|
||||
) -> Result<ValidationResult, Error> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
sender.send_message(
|
||||
CandidateValidationMessage::ValidateFromChainState(
|
||||
candidate,
|
||||
pov,
|
||||
tx,
|
||||
)
|
||||
).await;
|
||||
sender
|
||||
.send_message(CandidateValidationMessage::ValidateFromChainState(candidate, pov, tx))
|
||||
.await;
|
||||
|
||||
match rx.await {
|
||||
Ok(Ok(validation_result)) => Ok(validation_result),
|
||||
@@ -417,7 +400,8 @@ async fn request_candidate_validation(
|
||||
}
|
||||
}
|
||||
|
||||
type BackgroundValidationResult = Result<(CandidateReceipt, CandidateCommitments, Arc<PoV>), CandidateReceipt>;
|
||||
type BackgroundValidationResult =
|
||||
Result<(CandidateReceipt, CandidateCommitments, Arc<PoV>), CandidateReceipt>;
|
||||
|
||||
struct BackgroundValidationParams<S: overseer::SubsystemSender<AllMessages>, F> {
|
||||
sender: JobSender<S>,
|
||||
@@ -435,7 +419,7 @@ async fn validate_and_make_available(
|
||||
params: BackgroundValidationParams<
|
||||
impl SubsystemSender,
|
||||
impl Fn(BackgroundValidationResult) -> ValidatedCandidateCommand + Sync,
|
||||
>
|
||||
>,
|
||||
) -> Result<(), Error> {
|
||||
let BackgroundValidationParams {
|
||||
mut sender,
|
||||
@@ -451,27 +435,22 @@ async fn validate_and_make_available(
|
||||
|
||||
let pov = match pov {
|
||||
PoVData::Ready(pov) => pov,
|
||||
PoVData::FetchFromValidator {
|
||||
from_validator,
|
||||
candidate_hash,
|
||||
pov_hash,
|
||||
} => {
|
||||
PoVData::FetchFromValidator { from_validator, candidate_hash, pov_hash } => {
|
||||
let _span = span.as_ref().map(|s| s.child("request-pov"));
|
||||
match request_pov(
|
||||
&mut sender,
|
||||
relay_parent,
|
||||
from_validator,
|
||||
candidate_hash,
|
||||
pov_hash,
|
||||
).await {
|
||||
match request_pov(&mut sender, relay_parent, from_validator, candidate_hash, pov_hash)
|
||||
.await
|
||||
{
|
||||
Err(Error::FetchPoV) => {
|
||||
tx_command.send(ValidatedCandidateCommand::AttestNoPoV(candidate.hash())).await.map_err(Error::Mpsc)?;
|
||||
tx_command
|
||||
.send(ValidatedCandidateCommand::AttestNoPoV(candidate.hash()))
|
||||
.await
|
||||
.map_err(Error::Mpsc)?;
|
||||
return Ok(())
|
||||
}
|
||||
},
|
||||
Err(err) => return Err(err),
|
||||
Ok(pov) => pov,
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let v = {
|
||||
@@ -512,7 +491,8 @@ async fn validate_and_make_available(
|
||||
validation_data,
|
||||
candidate.descriptor.erasure_root,
|
||||
span.as_ref(),
|
||||
).await?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
match erasure_valid {
|
||||
Ok(()) => Ok((candidate, commitments, pov.clone())),
|
||||
@@ -527,7 +507,7 @@ async fn validate_and_make_available(
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ValidationResult::Invalid(reason) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
@@ -536,7 +516,7 @@ async fn validate_and_make_available(
|
||||
"Validation yielded an invalid candidate",
|
||||
);
|
||||
Err(candidate)
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
tx_command.send(make_command(res)).await.map_err(Into::into)
|
||||
@@ -591,7 +571,9 @@ impl CandidateBackingJob {
|
||||
match res {
|
||||
Ok((candidate, commitments, _)) => {
|
||||
// sanity check.
|
||||
if self.seconded.is_none() && !self.issued_statements.contains(&candidate_hash) {
|
||||
if self.seconded.is_none() &&
|
||||
!self.issued_statements.contains(&candidate_hash)
|
||||
{
|
||||
self.seconded = Some(candidate_hash);
|
||||
self.issued_statements.insert(candidate_hash);
|
||||
self.metrics.on_candidate_seconded();
|
||||
@@ -600,24 +582,26 @@ impl CandidateBackingJob {
|
||||
descriptor: candidate.descriptor.clone(),
|
||||
commitments,
|
||||
});
|
||||
if let Some(stmt) = self.sign_import_and_distribute_statement(
|
||||
sender,
|
||||
statement,
|
||||
root_span,
|
||||
).await? {
|
||||
sender.send_message(
|
||||
CollatorProtocolMessage::Seconded(self.parent, stmt)
|
||||
).await;
|
||||
if let Some(stmt) = self
|
||||
.sign_import_and_distribute_statement(sender, statement, root_span)
|
||||
.await?
|
||||
{
|
||||
sender
|
||||
.send_message(CollatorProtocolMessage::Seconded(
|
||||
self.parent,
|
||||
stmt,
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(candidate) => {
|
||||
sender.send_message(
|
||||
CollatorProtocolMessage::Invalid(self.parent, candidate)
|
||||
).await;
|
||||
}
|
||||
sender
|
||||
.send_message(CollatorProtocolMessage::Invalid(self.parent, candidate))
|
||||
.await;
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
ValidatedCandidateCommand::Attest(res) => {
|
||||
// We are done - avoid new validation spawns:
|
||||
self.fallbacks.remove(&candidate_hash);
|
||||
@@ -625,11 +609,12 @@ impl CandidateBackingJob {
|
||||
if !self.issued_statements.contains(&candidate_hash) {
|
||||
if res.is_ok() {
|
||||
let statement = Statement::Valid(candidate_hash);
|
||||
self.sign_import_and_distribute_statement(sender, statement, &root_span).await?;
|
||||
self.sign_import_and_distribute_statement(sender, statement, &root_span)
|
||||
.await?;
|
||||
}
|
||||
self.issued_statements.insert(candidate_hash);
|
||||
}
|
||||
}
|
||||
},
|
||||
ValidatedCandidateCommand::AttestNoPoV(candidate_hash) => {
|
||||
if let Some((attesting, span)) = self.fallbacks.get_mut(&candidate_hash) {
|
||||
if let Some(index) = attesting.backing.pop() {
|
||||
@@ -639,7 +624,6 @@ impl CandidateBackingJob {
|
||||
let attesting = attesting.clone();
|
||||
self.kick_off_validation_work(sender, attesting, c_span).await?
|
||||
}
|
||||
|
||||
} else {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
@@ -647,7 +631,7 @@ impl CandidateBackingJob {
|
||||
);
|
||||
debug_assert!(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -658,7 +642,7 @@ impl CandidateBackingJob {
|
||||
sender: &mut JobSender<impl SubsystemSender>,
|
||||
params: BackgroundValidationParams<
|
||||
impl SubsystemSender,
|
||||
impl Fn(BackgroundValidationResult) -> ValidatedCandidateCommand + Send + 'static + Sync
|
||||
impl Fn(BackgroundValidationResult) -> ValidatedCandidateCommand + Send + 'static + Sync,
|
||||
>,
|
||||
) -> Result<(), Error> {
|
||||
let candidate_hash = params.candidate.hash();
|
||||
@@ -666,10 +650,16 @@ impl CandidateBackingJob {
|
||||
// spawn background task.
|
||||
let bg = async move {
|
||||
if let Err(e) = validate_and_make_available(params).await {
|
||||
tracing::error!(target: LOG_TARGET, "Failed to validate and make available: {:?}", e);
|
||||
tracing::error!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to validate and make available: {:?}",
|
||||
e
|
||||
);
|
||||
}
|
||||
};
|
||||
sender.send_command(FromJobCommand::Spawn("Backing Validation", bg.boxed())).await?;
|
||||
sender
|
||||
.send_command(FromJobCommand::Spawn("Backing Validation", bg.boxed()))
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -685,13 +675,15 @@ impl CandidateBackingJob {
|
||||
pov: Arc<PoV>,
|
||||
) -> Result<(), Error> {
|
||||
// Check that candidate is collated by the right collator.
|
||||
if self.required_collator.as_ref()
|
||||
if self
|
||||
.required_collator
|
||||
.as_ref()
|
||||
.map_or(false, |c| c != &candidate.descriptor().collator)
|
||||
{
|
||||
sender.send_message(
|
||||
CollatorProtocolMessage::Invalid(self.parent, candidate.clone())
|
||||
).await;
|
||||
return Ok(());
|
||||
sender
|
||||
.send_message(CollatorProtocolMessage::Invalid(self.parent, candidate.clone()))
|
||||
.await;
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let candidate_hash = candidate.hash();
|
||||
@@ -723,8 +715,9 @@ impl CandidateBackingJob {
|
||||
n_validators: self.table_context.validators.len(),
|
||||
span,
|
||||
make_command: ValidatedCandidateCommand::Second,
|
||||
}
|
||||
).await?;
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -751,12 +744,12 @@ impl CandidateBackingJob {
|
||||
// collect the misbehaviors to avoid double mutable self borrow issues
|
||||
let misbehaviors: Vec<_> = self.table.drain_misbehaviors().collect();
|
||||
for (validator_id, report) in misbehaviors {
|
||||
sender.send_message(
|
||||
ProvisionerMessage::ProvisionableData(
|
||||
sender
|
||||
.send_message(ProvisionerMessage::ProvisionableData(
|
||||
self.parent,
|
||||
ProvisionableData::MisbehaviorReport(self.parent, validator_id, report)
|
||||
)
|
||||
).await;
|
||||
ProvisionableData::MisbehaviorReport(self.parent, validator_id, report),
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -777,14 +770,17 @@ impl CandidateBackingJob {
|
||||
let candidate_hash = statement.payload().candidate_hash();
|
||||
let import_statement_span = {
|
||||
// create a span only for candidates we're already aware of.
|
||||
self.get_unbacked_statement_child(root_span, candidate_hash, statement.validator_index())
|
||||
self.get_unbacked_statement_child(
|
||||
root_span,
|
||||
candidate_hash,
|
||||
statement.validator_index(),
|
||||
)
|
||||
};
|
||||
|
||||
if let Err(ValidatorIndexOutOfBounds) = self.dispatch_new_statement_to_dispute_coordinator(
|
||||
sender,
|
||||
candidate_hash,
|
||||
&statement,
|
||||
).await {
|
||||
if let Err(ValidatorIndexOutOfBounds) = self
|
||||
.dispatch_new_statement_to_dispute_coordinator(sender, candidate_hash, &statement)
|
||||
.await
|
||||
{
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
session_index = ?self.session_index,
|
||||
@@ -793,14 +789,15 @@ impl CandidateBackingJob {
|
||||
"Supposedly 'Signed' statement has validator index out of bounds."
|
||||
);
|
||||
|
||||
return Ok(None);
|
||||
return Ok(None)
|
||||
}
|
||||
|
||||
let stmt = primitive_statement_to_table(statement);
|
||||
|
||||
let summary = self.table.import_statement(&self.table_context, stmt);
|
||||
|
||||
let unbacked_span = if let Some(attested) = summary.as_ref()
|
||||
let unbacked_span = if let Some(attested) = summary
|
||||
.as_ref()
|
||||
.and_then(|s| self.table.attested_candidate(&s.candidate, &self.table_context))
|
||||
{
|
||||
let candidate_hash = attested.candidate.hash();
|
||||
@@ -808,9 +805,7 @@ impl CandidateBackingJob {
|
||||
if self.backed.insert(candidate_hash) {
|
||||
let span = self.remove_unbacked_span(&candidate_hash);
|
||||
|
||||
if let Some(backed) =
|
||||
table_attested_to_backed(attested, &self.table_context)
|
||||
{
|
||||
if let Some(backed) = table_attested_to_backed(attested, &self.table_context) {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
candidate_hash = ?candidate_hash,
|
||||
@@ -866,18 +861,11 @@ impl CandidateBackingJob {
|
||||
) -> Result<(), ValidatorIndexOutOfBounds> {
|
||||
// Dispatch the statement to the dispute coordinator.
|
||||
let validator_index = statement.validator_index();
|
||||
let signing_context = SigningContext {
|
||||
parent_hash: self.parent,
|
||||
session_index: self.session_index,
|
||||
};
|
||||
let signing_context =
|
||||
SigningContext { parent_hash: self.parent, session_index: self.session_index };
|
||||
|
||||
let validator_public = match self.table_context
|
||||
.validators
|
||||
.get(validator_index.0 as usize)
|
||||
{
|
||||
None => {
|
||||
return Err(ValidatorIndexOutOfBounds);
|
||||
}
|
||||
let validator_public = match self.table_context.validators.get(validator_index.0 as usize) {
|
||||
None => return Err(ValidatorIndexOutOfBounds),
|
||||
Some(v) => v,
|
||||
};
|
||||
|
||||
@@ -887,39 +875,36 @@ impl CandidateBackingJob {
|
||||
// Valid statements are only supposed to be imported
|
||||
// once we've seen at least one `Seconded` statement.
|
||||
self.table.get_candidate(&candidate_hash).map(|c| c.to_plain())
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let maybe_signed_dispute_statement = SignedDisputeStatement::from_backing_statement(
|
||||
statement.as_unchecked(),
|
||||
signing_context,
|
||||
validator_public.clone(),
|
||||
).ok();
|
||||
)
|
||||
.ok();
|
||||
|
||||
if let (Some(candidate_receipt), Some(dispute_statement))
|
||||
= (maybe_candidate_receipt, maybe_signed_dispute_statement)
|
||||
if let (Some(candidate_receipt), Some(dispute_statement)) =
|
||||
(maybe_candidate_receipt, maybe_signed_dispute_statement)
|
||||
{
|
||||
let (pending_confirmation, confirmation_rx) = oneshot::channel();
|
||||
sender.send_message(
|
||||
DisputeCoordinatorMessage::ImportStatements {
|
||||
sender
|
||||
.send_message(DisputeCoordinatorMessage::ImportStatements {
|
||||
candidate_hash,
|
||||
candidate_receipt,
|
||||
session: self.session_index,
|
||||
statements: vec![(dispute_statement, validator_index)],
|
||||
pending_confirmation,
|
||||
}
|
||||
).await;
|
||||
})
|
||||
.await;
|
||||
|
||||
match confirmation_rx.await {
|
||||
Err(oneshot::Canceled) => tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
"Dispute coordinator confirmation lost",
|
||||
),
|
||||
Ok(ImportStatementsResult::ValidImport) => {}
|
||||
Ok(ImportStatementsResult::InvalidImport) => tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to import statements of validity",
|
||||
),
|
||||
Err(oneshot::Canceled) =>
|
||||
tracing::warn!(target: LOG_TARGET, "Dispute coordinator confirmation lost",),
|
||||
Ok(ImportStatementsResult::ValidImport) => {},
|
||||
Ok(ImportStatementsResult::InvalidImport) =>
|
||||
tracing::warn!(target: LOG_TARGET, "Failed to import statements of validity",),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -936,7 +921,8 @@ impl CandidateBackingJob {
|
||||
CandidateBackingMessage::Second(relay_parent, candidate, pov) => {
|
||||
let _timer = self.metrics.time_process_second();
|
||||
|
||||
let span = root_span.child("second")
|
||||
let span = root_span
|
||||
.child("second")
|
||||
.with_stage(jaeger::Stage::CandidateBacking)
|
||||
.with_pov(&pov)
|
||||
.with_candidate(candidate.hash())
|
||||
@@ -944,7 +930,7 @@ impl CandidateBackingJob {
|
||||
|
||||
// Sanity check that candidate is from our assignment.
|
||||
if Some(candidate.descriptor().para_id) != self.assignment {
|
||||
return Ok(());
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
// If the message is a `CandidateBackingMessage::Second`, sign and dispatch a
|
||||
@@ -956,13 +942,15 @@ impl CandidateBackingJob {
|
||||
|
||||
if !self.issued_statements.contains(&candidate_hash) {
|
||||
let pov = Arc::new(pov);
|
||||
self.validate_and_second(&span, &root_span, sender, &candidate, pov).await?;
|
||||
self.validate_and_second(&span, &root_span, sender, &candidate, pov)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
CandidateBackingMessage::Statement(_relay_parent, statement) => {
|
||||
let _timer = self.metrics.time_process_statement();
|
||||
let _span = root_span.child("statement")
|
||||
let _span = root_span
|
||||
.child("statement")
|
||||
.with_stage(jaeger::Stage::CandidateBacking)
|
||||
.with_candidate(statement.payload().candidate_hash())
|
||||
.with_relay_parent(_relay_parent);
|
||||
@@ -972,20 +960,21 @@ impl CandidateBackingJob {
|
||||
Err(e) => return Err(e),
|
||||
Ok(()) => (),
|
||||
}
|
||||
}
|
||||
},
|
||||
CandidateBackingMessage::GetBackedCandidates(_, requested_candidates, tx) => {
|
||||
let _timer = self.metrics.time_get_backed_candidates();
|
||||
|
||||
let backed = requested_candidates
|
||||
.into_iter()
|
||||
.filter_map(|hash| {
|
||||
self.table.attested_candidate(&hash, &self.table_context)
|
||||
.and_then(|attested| table_attested_to_backed(attested, &self.table_context))
|
||||
self.table.attested_candidate(&hash, &self.table_context).and_then(
|
||||
|attested| table_attested_to_backed(attested, &self.table_context),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
tx.send(backed).map_err(|data| Error::Send(data))?;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -1013,15 +1002,13 @@ impl CandidateBackingJob {
|
||||
);
|
||||
|
||||
// Check that candidate is collated by the right collator.
|
||||
if self.required_collator.as_ref()
|
||||
.map_or(false, |c| c != &descriptor.collator)
|
||||
{
|
||||
if self.required_collator.as_ref().map_or(false, |c| c != &descriptor.collator) {
|
||||
// If not, we've got the statement in the table but we will
|
||||
// not issue validation work for it.
|
||||
//
|
||||
// Act as though we've issued a statement.
|
||||
self.issued_statements.insert(candidate_hash);
|
||||
return Ok(());
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let bg_sender = sender.clone();
|
||||
@@ -1043,7 +1030,8 @@ impl CandidateBackingJob {
|
||||
span,
|
||||
make_command: ValidatedCandidateCommand::Attest,
|
||||
},
|
||||
).await
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Import the statement and kick off validation work if it is a part of our assignment.
|
||||
@@ -1068,7 +1056,11 @@ impl CandidateBackingJob {
|
||||
);
|
||||
|
||||
let attesting = AttestingData {
|
||||
candidate: self.table.get_candidate(&candidate_hash).ok_or(Error::CandidateNotFound)?.to_plain(),
|
||||
candidate: self
|
||||
.table
|
||||
.get_candidate(&candidate_hash)
|
||||
.ok_or(Error::CandidateNotFound)?
|
||||
.to_plain(),
|
||||
pov_hash: receipt.descriptor.pov_hash,
|
||||
from_validator: statement.validator_index(),
|
||||
backing: Vec::new(),
|
||||
@@ -1076,10 +1068,9 @@ impl CandidateBackingJob {
|
||||
let child = span.as_ref().map(|s| s.child("try"));
|
||||
self.fallbacks.insert(summary.candidate, (attesting.clone(), span));
|
||||
(attesting, child)
|
||||
}
|
||||
},
|
||||
Statement::Valid(candidate_hash) => {
|
||||
if let Some((attesting, span)) = self.fallbacks.get_mut(candidate_hash) {
|
||||
|
||||
let our_index = self.table_context.validator.as_ref().map(|v| v.index());
|
||||
if our_index == Some(statement.validator_index()) {
|
||||
return Ok(())
|
||||
@@ -1097,20 +1088,17 @@ impl CandidateBackingJob {
|
||||
} else {
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
self.kick_off_validation_work(
|
||||
sender,
|
||||
attesting,
|
||||
span,
|
||||
).await?;
|
||||
self.kick_off_validation_work(sender, attesting, span).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn sign_statement(&self, statement: Statement) -> Option<SignedFullStatement> {
|
||||
let signed = self.table_context
|
||||
let signed = self
|
||||
.table_context
|
||||
.validator
|
||||
.as_ref()?
|
||||
.sign(self.keystore.clone(), statement)
|
||||
@@ -1126,7 +1114,7 @@ impl CandidateBackingJob {
|
||||
&mut self,
|
||||
parent_span: &jaeger::Span,
|
||||
hash: CandidateHash,
|
||||
para_id: Option<ParaId>
|
||||
para_id: Option<ParaId>,
|
||||
) -> Option<&jaeger::Span> {
|
||||
if !self.backed.contains(&hash) {
|
||||
// only add if we don't consider this backed.
|
||||
@@ -1150,12 +1138,11 @@ impl CandidateBackingJob {
|
||||
hash: CandidateHash,
|
||||
para_id: ParaId,
|
||||
) -> Option<jaeger::Span> {
|
||||
self.insert_or_get_unbacked_span(parent_span, hash, Some(para_id))
|
||||
.map(|span| {
|
||||
span.child("validation")
|
||||
.with_candidate(hash)
|
||||
.with_stage(Stage::CandidateBacking)
|
||||
})
|
||||
self.insert_or_get_unbacked_span(parent_span, hash, Some(para_id)).map(|span| {
|
||||
span.child("validation")
|
||||
.with_candidate(hash)
|
||||
.with_stage(Stage::CandidateBacking)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_unbacked_statement_child(
|
||||
@@ -1220,12 +1207,12 @@ impl util::JobTrait for CandidateBackingJob {
|
||||
request_validators(parent, &mut sender).await,
|
||||
request_validator_groups(parent, &mut sender).await,
|
||||
request_session_index_for_child(parent, &mut sender).await,
|
||||
request_from_runtime(
|
||||
parent,
|
||||
&mut sender,
|
||||
|tx| RuntimeApiRequest::AvailabilityCores(tx),
|
||||
).await,
|
||||
).map_err(Error::JoinMultiple)?;
|
||||
request_from_runtime(parent, &mut sender, |tx| {
|
||||
RuntimeApiRequest::AvailabilityCores(tx)
|
||||
},)
|
||||
.await,
|
||||
)
|
||||
.map_err(Error::JoinMultiple)?;
|
||||
|
||||
let validators = try_runtime_api!(validators);
|
||||
let (validator_groups, group_rotation_info) = try_runtime_api!(groups);
|
||||
@@ -1236,23 +1223,22 @@ impl util::JobTrait for CandidateBackingJob {
|
||||
let _span = span.child("validator-construction");
|
||||
|
||||
let signing_context = SigningContext { parent_hash: parent, session_index };
|
||||
let validator = match Validator::construct(
|
||||
&validators,
|
||||
signing_context.clone(),
|
||||
keystore.clone(),
|
||||
).await {
|
||||
Ok(v) => Some(v),
|
||||
Err(util::Error::NotAValidator) => None,
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
err = ?e,
|
||||
"Cannot participate in candidate backing",
|
||||
);
|
||||
let validator =
|
||||
match Validator::construct(&validators, signing_context.clone(), keystore.clone())
|
||||
.await
|
||||
{
|
||||
Ok(v) => Some(v),
|
||||
Err(util::Error::NotAValidator) => None,
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
err = ?e,
|
||||
"Cannot participate in candidate backing",
|
||||
);
|
||||
|
||||
return Ok(())
|
||||
}
|
||||
};
|
||||
return Ok(())
|
||||
},
|
||||
};
|
||||
|
||||
drop(_span);
|
||||
let mut assignments_span = span.child("compute-assignments");
|
||||
@@ -1277,22 +1263,18 @@ impl util::JobTrait for CandidateBackingJob {
|
||||
}
|
||||
}
|
||||
|
||||
let table_context = TableContext {
|
||||
groups,
|
||||
validators,
|
||||
validator,
|
||||
};
|
||||
let table_context = TableContext { groups, validators, validator };
|
||||
|
||||
let (assignment, required_collator) = match assignment {
|
||||
None => {
|
||||
assignments_span.add_string_tag("assigned", "false");
|
||||
(None, None)
|
||||
}
|
||||
},
|
||||
Some((assignment, required_collator)) => {
|
||||
assignments_span.add_string_tag("assigned", "true");
|
||||
assignments_span.add_para_id(assignment);
|
||||
(Some(assignment), required_collator)
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
drop(assignments_span);
|
||||
@@ -1320,7 +1302,8 @@ impl util::JobTrait for CandidateBackingJob {
|
||||
drop(_span);
|
||||
|
||||
job.run_loop(sender, rx_to, span).await
|
||||
}.boxed()
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1361,7 +1344,9 @@ impl Metrics {
|
||||
}
|
||||
|
||||
/// Provide a timer for handling `CandidateBackingMessage::GetBackedCandidates` which observes on drop.
|
||||
fn time_get_backed_candidates(&self) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
|
||||
fn time_get_backed_candidates(
|
||||
&self,
|
||||
) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
|
||||
self.0.as_ref().map(|metrics| metrics.get_backed_candidates.start_timer())
|
||||
}
|
||||
}
|
||||
@@ -1384,30 +1369,24 @@ impl metrics::Metrics for Metrics {
|
||||
registry,
|
||||
)?,
|
||||
process_second: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_candidate_backing_process_second",
|
||||
"Time spent within `candidate_backing::process_second`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_candidate_backing_process_second",
|
||||
"Time spent within `candidate_backing::process_second`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
process_statement: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_candidate_backing_process_statement",
|
||||
"Time spent within `candidate_backing::process_statement`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_candidate_backing_process_statement",
|
||||
"Time spent within `candidate_backing::process_statement`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
get_backed_candidates: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_candidate_backing_get_backed_candidates",
|
||||
"Time spent within `candidate_backing::get_backed_candidates`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_candidate_backing_get_backed_candidates",
|
||||
"Time spent within `candidate_backing::get_backed_candidates`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
};
|
||||
@@ -1416,5 +1395,5 @@ impl metrics::Metrics for Metrics {
|
||||
}
|
||||
|
||||
/// The candidate backing subsystem.
|
||||
pub type CandidateBackingSubsystem<Spawner>
|
||||
= polkadot_node_subsystem_util::JobSubsystem<CandidateBackingJob, Spawner>;
|
||||
pub type CandidateBackingSubsystem<Spawner> =
|
||||
polkadot_node_subsystem_util::JobSubsystem<CandidateBackingJob, Spawner>;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,24 +18,32 @@
|
||||
|
||||
#![deny(unused_crate_dependencies)]
|
||||
#![warn(missing_docs)]
|
||||
#![recursion_limit="256"]
|
||||
#![recursion_limit = "256"]
|
||||
|
||||
use futures::{channel::{mpsc, oneshot}, lock::Mutex, prelude::*, future, Future};
|
||||
use sp_keystore::{Error as KeystoreError, SyncCryptoStorePtr};
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
future,
|
||||
lock::Mutex,
|
||||
prelude::*,
|
||||
Future,
|
||||
};
|
||||
use polkadot_node_subsystem::{
|
||||
jaeger, PerLeafSpan, SubsystemSender,
|
||||
messages::{
|
||||
AvailabilityStoreMessage, BitfieldDistributionMessage,
|
||||
BitfieldSigningMessage, RuntimeApiMessage, RuntimeApiRequest,
|
||||
},
|
||||
errors::RuntimeApiError,
|
||||
jaeger,
|
||||
messages::{
|
||||
AvailabilityStoreMessage, BitfieldDistributionMessage, BitfieldSigningMessage,
|
||||
RuntimeApiMessage, RuntimeApiRequest,
|
||||
},
|
||||
PerLeafSpan, SubsystemSender,
|
||||
};
|
||||
use polkadot_node_subsystem_util::{
|
||||
self as util, JobSubsystem, JobTrait, Validator, metrics::{self, prometheus},
|
||||
JobSender,
|
||||
self as util,
|
||||
metrics::{self, prometheus},
|
||||
JobSender, JobSubsystem, JobTrait, Validator,
|
||||
};
|
||||
use polkadot_primitives::v1::{AvailabilityBitfield, CoreState, Hash, ValidatorIndex};
|
||||
use std::{pin::Pin, time::Duration, iter::FromIterator, sync::Arc};
|
||||
use sp_keystore::{Error as KeystoreError, SyncCryptoStorePtr};
|
||||
use std::{iter::FromIterator, pin::Pin, sync::Arc, time::Duration};
|
||||
use wasm_timer::{Delay, Instant};
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -45,7 +53,6 @@ mod tests;
|
||||
const JOB_DELAY: Duration = Duration::from_millis(1500);
|
||||
const LOG_TARGET: &str = "parachain::bitfield-signing";
|
||||
|
||||
|
||||
/// Each `BitfieldSigningJob` prepares a signed bitfield for a single relay parent.
|
||||
pub struct BitfieldSigningJob;
|
||||
|
||||
@@ -92,7 +99,8 @@ async fn get_core_availability(
|
||||
core.candidate_hash,
|
||||
validator_idx,
|
||||
tx,
|
||||
).into(),
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -119,15 +127,15 @@ async fn get_availability_cores(
|
||||
) -> Result<Vec<CoreState>, Error> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
sender
|
||||
.send_message(RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
RuntimeApiRequest::AvailabilityCores(tx),
|
||||
).into())
|
||||
.send_message(
|
||||
RuntimeApiMessage::Request(relay_parent, RuntimeApiRequest::AvailabilityCores(tx))
|
||||
.into(),
|
||||
)
|
||||
.await;
|
||||
match rx.await {
|
||||
Ok(Ok(out)) => Ok(out),
|
||||
Ok(Err(runtime_err)) => Err(runtime_err.into()),
|
||||
Err(err) => Err(err.into())
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,9 +165,11 @@ async fn construct_availability_bitfield(
|
||||
// Handle all cores concurrently
|
||||
// `try_join_all` returns all results in the same order as the input futures.
|
||||
let results = future::try_join_all(
|
||||
availability_cores.iter()
|
||||
availability_cores
|
||||
.iter()
|
||||
.map(|core| get_core_availability(core, validator_idx, &sender, span)),
|
||||
).await?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
@@ -206,12 +216,10 @@ impl metrics::Metrics for Metrics {
|
||||
registry,
|
||||
)?,
|
||||
run: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_bitfield_signing_run",
|
||||
"Time spent within `bitfield_signing::run`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_bitfield_signing_run",
|
||||
"Time spent within `bitfield_signing::run`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
};
|
||||
@@ -244,7 +252,8 @@ impl JobTrait for BitfieldSigningJob {
|
||||
|
||||
// now do all the work we can before we need to wait for the availability store
|
||||
// if we're not a validator, we can just succeed effortlessly
|
||||
let validator = match Validator::new(relay_parent, keystore.clone(), &mut sender).await {
|
||||
let validator = match Validator::new(relay_parent, keystore.clone(), &mut sender).await
|
||||
{
|
||||
Ok(validator) => validator,
|
||||
Err(util::Error::NotAValidator) => return Ok(()),
|
||||
Err(err) => return Err(Error::Util(err)),
|
||||
@@ -260,19 +269,19 @@ impl JobTrait for BitfieldSigningJob {
|
||||
drop(_span);
|
||||
let span_availability = span.child("availability");
|
||||
|
||||
let bitfield =
|
||||
match construct_availability_bitfield(
|
||||
relay_parent,
|
||||
&span_availability,
|
||||
validator.index(),
|
||||
sender.subsystem_sender(),
|
||||
).await
|
||||
let bitfield = match construct_availability_bitfield(
|
||||
relay_parent,
|
||||
&span_availability,
|
||||
validator.index(),
|
||||
sender.subsystem_sender(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Err(Error::Runtime(runtime_err)) => {
|
||||
// Don't take down the node on runtime API errors.
|
||||
tracing::warn!(target: LOG_TARGET, err = ?runtime_err, "Encountered a runtime API error");
|
||||
return Ok(());
|
||||
}
|
||||
return Ok(())
|
||||
},
|
||||
Err(err) => return Err(err),
|
||||
Ok(bitfield) => bitfield,
|
||||
};
|
||||
@@ -280,7 +289,8 @@ impl JobTrait for BitfieldSigningJob {
|
||||
drop(span_availability);
|
||||
let _span = span.child("signing");
|
||||
|
||||
let signed_bitfield = match validator.sign(keystore.clone(), bitfield)
|
||||
let signed_bitfield = match validator
|
||||
.sign(keystore.clone(), bitfield)
|
||||
.await
|
||||
.map_err(|e| Error::Keystore(e))?
|
||||
{
|
||||
@@ -290,8 +300,8 @@ impl JobTrait for BitfieldSigningJob {
|
||||
target: LOG_TARGET,
|
||||
"Key was found at construction, but while signing it could not be found.",
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
return Ok(())
|
||||
},
|
||||
};
|
||||
|
||||
metrics.on_bitfield_signed();
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
use futures::{pin_mut, executor::block_on};
|
||||
use polkadot_primitives::v1::{CandidateHash, OccupiedCore};
|
||||
use futures::{executor::block_on, pin_mut};
|
||||
use polkadot_node_subsystem::messages::AllMessages;
|
||||
use polkadot_primitives::v1::{CandidateHash, OccupiedCore};
|
||||
|
||||
fn occupied_core(para_id: u32, candidate_hash: CandidateHash) -> CoreState {
|
||||
CoreState::Occupied(OccupiedCore {
|
||||
@@ -44,7 +44,8 @@ fn construct_availability_bitfield_works() {
|
||||
&jaeger::Span::Disabled,
|
||||
validator_index,
|
||||
&mut sender,
|
||||
).fuse();
|
||||
)
|
||||
.fuse();
|
||||
pin_mut!(future);
|
||||
|
||||
let hash_a = CandidateHash(Hash::repeat_byte(1));
|
||||
|
||||
@@ -23,34 +23,32 @@
|
||||
#![deny(unused_crate_dependencies, unused_results)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use polkadot_node_core_pvf::{
|
||||
InvalidCandidate as WasmInvalidCandidate, Pvf, ValidationError, ValidationHost,
|
||||
};
|
||||
use polkadot_node_primitives::{
|
||||
BlockData, InvalidCandidate, PoV, ValidationResult, POV_BOMB_LIMIT, VALIDATION_CODE_BOMB_LIMIT,
|
||||
};
|
||||
use polkadot_node_subsystem::{
|
||||
overseer,
|
||||
SubsystemContext, SpawnedSubsystem, SubsystemResult, SubsystemError,
|
||||
FromOverseer, OverseerSignal,
|
||||
messages::{
|
||||
CandidateValidationMessage, RuntimeApiMessage,
|
||||
ValidationFailed, RuntimeApiRequest,
|
||||
},
|
||||
errors::RuntimeApiError,
|
||||
messages::{
|
||||
CandidateValidationMessage, RuntimeApiMessage, RuntimeApiRequest, ValidationFailed,
|
||||
},
|
||||
overseer, FromOverseer, OverseerSignal, SpawnedSubsystem, SubsystemContext, SubsystemError,
|
||||
SubsystemResult,
|
||||
};
|
||||
use polkadot_node_subsystem_util::metrics::{self, prometheus};
|
||||
use polkadot_node_primitives::{
|
||||
VALIDATION_CODE_BOMB_LIMIT, POV_BOMB_LIMIT, ValidationResult, InvalidCandidate, PoV, BlockData,
|
||||
};
|
||||
use polkadot_primitives::v1::{
|
||||
ValidationCode, CandidateDescriptor, PersistedValidationData,
|
||||
OccupiedCoreAssumption, Hash, CandidateCommitments,
|
||||
};
|
||||
use polkadot_parachain::primitives::{ValidationParams, ValidationResult as WasmValidationResult};
|
||||
use polkadot_node_core_pvf::{Pvf, ValidationHost, ValidationError, InvalidCandidate as WasmInvalidCandidate};
|
||||
use polkadot_primitives::v1::{
|
||||
CandidateCommitments, CandidateDescriptor, Hash, OccupiedCoreAssumption,
|
||||
PersistedValidationData, ValidationCode,
|
||||
};
|
||||
|
||||
use parity_scale_codec::Encode;
|
||||
|
||||
use futures::channel::oneshot;
|
||||
use futures::prelude::*;
|
||||
use futures::{channel::oneshot, prelude::*};
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::path::PathBuf;
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
@@ -81,7 +79,7 @@ impl CandidateValidationSubsystem {
|
||||
///
|
||||
/// Check out [`IsolationStrategy`] to get more details.
|
||||
pub fn with_config(config: Config, metrics: Metrics) -> Self {
|
||||
CandidateValidationSubsystem { config, metrics, }
|
||||
CandidateValidationSubsystem { config, metrics }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,13 +89,11 @@ where
|
||||
Context: overseer::SubsystemContext<Message = CandidateValidationMessage>,
|
||||
{
|
||||
fn start(self, ctx: Context) -> SpawnedSubsystem {
|
||||
let future = run(ctx, self.metrics, self.config.artifacts_cache_path, self.config.program_path)
|
||||
.map_err(|e| SubsystemError::with_origin("candidate-validation", e))
|
||||
.boxed();
|
||||
SpawnedSubsystem {
|
||||
name: "candidate-validation-subsystem",
|
||||
future,
|
||||
}
|
||||
let future =
|
||||
run(ctx, self.metrics, self.config.artifacts_cache_path, self.config.program_path)
|
||||
.map_err(|e| SubsystemError::with_origin("candidate-validation", e))
|
||||
.boxed();
|
||||
SpawnedSubsystem { name: "candidate-validation-subsystem", future }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,8 +114,8 @@ where
|
||||
|
||||
loop {
|
||||
match ctx.recv().await? {
|
||||
FromOverseer::Signal(OverseerSignal::ActiveLeaves(_)) => {}
|
||||
FromOverseer::Signal(OverseerSignal::BlockFinalized(..)) => {}
|
||||
FromOverseer::Signal(OverseerSignal::ActiveLeaves(_)) => {},
|
||||
FromOverseer::Signal(OverseerSignal::BlockFinalized(..)) => {},
|
||||
FromOverseer::Signal(OverseerSignal::Conclude) => return Ok(()),
|
||||
FromOverseer::Communication { msg } => match msg {
|
||||
CandidateValidationMessage::ValidateFromChainState(
|
||||
@@ -135,16 +131,17 @@ where
|
||||
descriptor,
|
||||
pov,
|
||||
&metrics,
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Ok(x) => {
|
||||
metrics.on_validation_event(&x);
|
||||
let _ = response_sender.send(x);
|
||||
}
|
||||
},
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
},
|
||||
CandidateValidationMessage::ValidateFromExhaustive(
|
||||
persisted_validation_data,
|
||||
validation_code,
|
||||
@@ -161,7 +158,8 @@ where
|
||||
descriptor,
|
||||
pov,
|
||||
&metrics,
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Ok(x) => {
|
||||
@@ -175,8 +173,8 @@ where
|
||||
},
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -191,12 +189,7 @@ where
|
||||
Context: SubsystemContext<Message = CandidateValidationMessage>,
|
||||
Context: overseer::SubsystemContext<Message = CandidateValidationMessage>,
|
||||
{
|
||||
ctx.send_message(
|
||||
RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
request,
|
||||
)
|
||||
).await;
|
||||
ctx.send_message(RuntimeApiMessage::Request(relay_parent, request)).await;
|
||||
|
||||
receiver.await.map_err(Into::into)
|
||||
}
|
||||
@@ -222,44 +215,38 @@ where
|
||||
let d = runtime_api_request(
|
||||
ctx,
|
||||
descriptor.relay_parent,
|
||||
RuntimeApiRequest::PersistedValidationData(
|
||||
descriptor.para_id,
|
||||
assumption,
|
||||
tx,
|
||||
),
|
||||
RuntimeApiRequest::PersistedValidationData(descriptor.para_id, assumption, tx),
|
||||
rx,
|
||||
).await?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
match d {
|
||||
Ok(None) | Err(_) => {
|
||||
return Ok(AssumptionCheckOutcome::BadRequest);
|
||||
}
|
||||
Ok(None) | Err(_) => return Ok(AssumptionCheckOutcome::BadRequest),
|
||||
Ok(Some(d)) => d,
|
||||
}
|
||||
};
|
||||
|
||||
let persisted_validation_data_hash = validation_data.hash();
|
||||
|
||||
SubsystemResult::Ok(if descriptor.persisted_validation_data_hash == persisted_validation_data_hash {
|
||||
let (code_tx, code_rx) = oneshot::channel();
|
||||
let validation_code = runtime_api_request(
|
||||
ctx,
|
||||
descriptor.relay_parent,
|
||||
RuntimeApiRequest::ValidationCode(
|
||||
descriptor.para_id,
|
||||
assumption,
|
||||
code_tx,
|
||||
),
|
||||
code_rx,
|
||||
).await?;
|
||||
SubsystemResult::Ok(
|
||||
if descriptor.persisted_validation_data_hash == persisted_validation_data_hash {
|
||||
let (code_tx, code_rx) = oneshot::channel();
|
||||
let validation_code = runtime_api_request(
|
||||
ctx,
|
||||
descriptor.relay_parent,
|
||||
RuntimeApiRequest::ValidationCode(descriptor.para_id, assumption, code_tx),
|
||||
code_rx,
|
||||
)
|
||||
.await?;
|
||||
|
||||
match validation_code {
|
||||
Ok(None) | Err(_) => AssumptionCheckOutcome::BadRequest,
|
||||
Ok(Some(v)) => AssumptionCheckOutcome::Matches(validation_data, v),
|
||||
}
|
||||
} else {
|
||||
AssumptionCheckOutcome::DoesNotMatch
|
||||
})
|
||||
match validation_code {
|
||||
Ok(None) | Err(_) => AssumptionCheckOutcome::BadRequest,
|
||||
Ok(Some(v)) => AssumptionCheckOutcome::Matches(validation_data, v),
|
||||
}
|
||||
} else {
|
||||
AssumptionCheckOutcome::DoesNotMatch
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
async fn find_assumed_validation_data<Context>(
|
||||
@@ -310,18 +297,16 @@ where
|
||||
{
|
||||
let (validation_data, validation_code) =
|
||||
match find_assumed_validation_data(ctx, &descriptor).await? {
|
||||
AssumptionCheckOutcome::Matches(validation_data, validation_code) => {
|
||||
(validation_data, validation_code)
|
||||
}
|
||||
AssumptionCheckOutcome::Matches(validation_data, validation_code) =>
|
||||
(validation_data, validation_code),
|
||||
AssumptionCheckOutcome::DoesNotMatch => {
|
||||
// If neither the assumption of the occupied core having the para included or the assumption
|
||||
// of the occupied core timing out are valid, then the persisted_validation_data_hash in the descriptor
|
||||
// is not based on the relay parent and is thus invalid.
|
||||
return Ok(Ok(ValidationResult::Invalid(InvalidCandidate::BadParent)));
|
||||
}
|
||||
AssumptionCheckOutcome::BadRequest => {
|
||||
return Ok(Err(ValidationFailed("Assumption Check: Bad request".into())));
|
||||
}
|
||||
return Ok(Ok(ValidationResult::Invalid(InvalidCandidate::BadParent)))
|
||||
},
|
||||
AssumptionCheckOutcome::BadRequest =>
|
||||
return Ok(Err(ValidationFailed("Assumption Check: Bad request".into()))),
|
||||
};
|
||||
|
||||
let validation_result = validate_candidate_exhaustive(
|
||||
@@ -344,15 +329,10 @@ where
|
||||
)
|
||||
.await?
|
||||
{
|
||||
Ok(true) => {}
|
||||
Ok(false) => {
|
||||
return Ok(Ok(ValidationResult::Invalid(
|
||||
InvalidCandidate::InvalidOutputs,
|
||||
)));
|
||||
}
|
||||
Err(_) => {
|
||||
return Ok(Err(ValidationFailed("Check Validation Outputs: Bad request".into())));
|
||||
}
|
||||
Ok(true) => {},
|
||||
Ok(false) => return Ok(Ok(ValidationResult::Invalid(InvalidCandidate::InvalidOutputs))),
|
||||
Err(_) =>
|
||||
return Ok(Err(ValidationFailed("Check Validation Outputs: Bad request".into()))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,7 +355,7 @@ async fn validate_candidate_exhaustive(
|
||||
&*pov,
|
||||
&validation_code,
|
||||
) {
|
||||
return Ok(Ok(ValidationResult::Invalid(e)));
|
||||
return Ok(Ok(ValidationResult::Invalid(e)))
|
||||
}
|
||||
|
||||
let raw_validation_code = match sp_maybe_compressed_blob::decompress(
|
||||
@@ -387,22 +367,20 @@ async fn validate_candidate_exhaustive(
|
||||
tracing::debug!(target: LOG_TARGET, err=?e, "Invalid validation code");
|
||||
|
||||
// If the validation code is invalid, the candidate certainly is.
|
||||
return Ok(Ok(ValidationResult::Invalid(InvalidCandidate::CodeDecompressionFailure)));
|
||||
}
|
||||
return Ok(Ok(ValidationResult::Invalid(InvalidCandidate::CodeDecompressionFailure)))
|
||||
},
|
||||
};
|
||||
|
||||
let raw_block_data = match sp_maybe_compressed_blob::decompress(
|
||||
&pov.block_data.0,
|
||||
POV_BOMB_LIMIT,
|
||||
) {
|
||||
Ok(block_data) => BlockData(block_data.to_vec()),
|
||||
Err(e) => {
|
||||
tracing::debug!(target: LOG_TARGET, err=?e, "Invalid PoV code");
|
||||
let raw_block_data =
|
||||
match sp_maybe_compressed_blob::decompress(&pov.block_data.0, POV_BOMB_LIMIT) {
|
||||
Ok(block_data) => BlockData(block_data.to_vec()),
|
||||
Err(e) => {
|
||||
tracing::debug!(target: LOG_TARGET, err=?e, "Invalid PoV code");
|
||||
|
||||
// If the PoV is invalid, the candidate certainly is.
|
||||
return Ok(Ok(ValidationResult::Invalid(InvalidCandidate::PoVDecompressionFailure)));
|
||||
}
|
||||
};
|
||||
// If the PoV is invalid, the candidate certainly is.
|
||||
return Ok(Ok(ValidationResult::Invalid(InvalidCandidate::PoVDecompressionFailure)))
|
||||
},
|
||||
};
|
||||
|
||||
let params = ValidationParams {
|
||||
parent_head: persisted_validation_data.parent_head.clone(),
|
||||
@@ -411,11 +389,8 @@ async fn validate_candidate_exhaustive(
|
||||
relay_parent_storage_root: persisted_validation_data.relay_parent_storage_root,
|
||||
};
|
||||
|
||||
let result =
|
||||
validation_backend.validate_candidate(
|
||||
raw_validation_code.to_vec(),
|
||||
params
|
||||
)
|
||||
let result = validation_backend
|
||||
.validate_candidate(raw_validation_code.to_vec(), params)
|
||||
.await;
|
||||
|
||||
if let Err(ref e) = result {
|
||||
@@ -434,9 +409,11 @@ async fn validate_candidate_exhaustive(
|
||||
Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::WorkerReportedError(e))) =>
|
||||
Ok(ValidationResult::Invalid(InvalidCandidate::ExecutionError(e))),
|
||||
Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::AmbigiousWorkerDeath)) =>
|
||||
Ok(ValidationResult::Invalid(InvalidCandidate::ExecutionError("ambigious worker death".to_string()))),
|
||||
Ok(ValidationResult::Invalid(InvalidCandidate::ExecutionError(
|
||||
"ambigious worker death".to_string(),
|
||||
))),
|
||||
|
||||
Ok(res) => {
|
||||
Ok(res) =>
|
||||
if res.head_data.hash() != descriptor.para_head {
|
||||
Ok(ValidationResult::Invalid(InvalidCandidate::ParaHeadHashMismatch))
|
||||
} else {
|
||||
@@ -449,8 +426,7 @@ async fn validate_candidate_exhaustive(
|
||||
hrmp_watermark: res.hrmp_watermark,
|
||||
};
|
||||
Ok(ValidationResult::Valid(outputs, persisted_validation_data))
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
@@ -461,7 +437,7 @@ trait ValidationBackend {
|
||||
async fn validate_candidate(
|
||||
&mut self,
|
||||
raw_validation_code: Vec<u8>,
|
||||
params: ValidationParams
|
||||
params: ValidationParams,
|
||||
) -> Result<WasmValidationResult, ValidationError>;
|
||||
}
|
||||
|
||||
@@ -470,16 +446,22 @@ impl ValidationBackend for &'_ mut ValidationHost {
|
||||
async fn validate_candidate(
|
||||
&mut self,
|
||||
raw_validation_code: Vec<u8>,
|
||||
params: ValidationParams
|
||||
params: ValidationParams,
|
||||
) -> Result<WasmValidationResult, ValidationError> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
if let Err(err) = self.execute_pvf(
|
||||
Pvf::from_code(raw_validation_code),
|
||||
params.encode(),
|
||||
polkadot_node_core_pvf::Priority::Normal,
|
||||
tx,
|
||||
).await {
|
||||
return Err(ValidationError::InternalError(format!("cannot send pvf to the validation host: {:?}", err)));
|
||||
if let Err(err) = self
|
||||
.execute_pvf(
|
||||
Pvf::from_code(raw_validation_code),
|
||||
params.encode(),
|
||||
polkadot_node_core_pvf::Priority::Normal,
|
||||
tx,
|
||||
)
|
||||
.await
|
||||
{
|
||||
return Err(ValidationError::InternalError(format!(
|
||||
"cannot send pvf to the validation host: {:?}",
|
||||
err
|
||||
)))
|
||||
}
|
||||
|
||||
let validation_result = rx
|
||||
@@ -503,19 +485,19 @@ fn perform_basic_checks(
|
||||
|
||||
let encoded_pov_size = pov.encoded_size();
|
||||
if encoded_pov_size > max_pov_size as usize {
|
||||
return Err(InvalidCandidate::ParamsTooLarge(encoded_pov_size as u64));
|
||||
return Err(InvalidCandidate::ParamsTooLarge(encoded_pov_size as u64))
|
||||
}
|
||||
|
||||
if pov_hash != candidate.pov_hash {
|
||||
return Err(InvalidCandidate::PoVHashMismatch);
|
||||
return Err(InvalidCandidate::PoVHashMismatch)
|
||||
}
|
||||
|
||||
if validation_code_hash != candidate.validation_code_hash {
|
||||
return Err(InvalidCandidate::CodeHashMismatch);
|
||||
return Err(InvalidCandidate::CodeHashMismatch)
|
||||
}
|
||||
|
||||
if let Err(()) = candidate.check_collator_signature() {
|
||||
return Err(InvalidCandidate::BadSignature);
|
||||
return Err(InvalidCandidate::BadSignature)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -551,18 +533,26 @@ impl Metrics {
|
||||
}
|
||||
|
||||
/// Provide a timer for `validate_from_chain_state` which observes on drop.
|
||||
fn time_validate_from_chain_state(&self) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
|
||||
fn time_validate_from_chain_state(
|
||||
&self,
|
||||
) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
|
||||
self.0.as_ref().map(|metrics| metrics.validate_from_chain_state.start_timer())
|
||||
}
|
||||
|
||||
/// Provide a timer for `validate_from_exhaustive` which observes on drop.
|
||||
fn time_validate_from_exhaustive(&self) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
|
||||
fn time_validate_from_exhaustive(
|
||||
&self,
|
||||
) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
|
||||
self.0.as_ref().map(|metrics| metrics.validate_from_exhaustive.start_timer())
|
||||
}
|
||||
|
||||
/// Provide a timer for `validate_candidate_exhaustive` which observes on drop.
|
||||
fn time_validate_candidate_exhaustive(&self) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
|
||||
self.0.as_ref().map(|metrics| metrics.validate_candidate_exhaustive.start_timer())
|
||||
fn time_validate_candidate_exhaustive(
|
||||
&self,
|
||||
) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
|
||||
self.0
|
||||
.as_ref()
|
||||
.map(|metrics| metrics.validate_candidate_exhaustive.start_timer())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -580,30 +570,24 @@ impl metrics::Metrics for Metrics {
|
||||
registry,
|
||||
)?,
|
||||
validate_from_chain_state: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_candidate_validation_validate_from_chain_state",
|
||||
"Time spent within `candidate_validation::validate_from_chain_state`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_candidate_validation_validate_from_chain_state",
|
||||
"Time spent within `candidate_validation::validate_from_chain_state`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
validate_from_exhaustive: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_candidate_validation_validate_from_exhaustive",
|
||||
"Time spent within `candidate_validation::validate_from_exhaustive`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_candidate_validation_validate_from_exhaustive",
|
||||
"Time spent within `candidate_validation::validate_from_exhaustive`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
validate_candidate_exhaustive: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_candidate_validation_validate_candidate_exhaustive",
|
||||
"Time spent within `candidate_validation::validate_candidate_exhaustive`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_candidate_validation_validate_candidate_exhaustive",
|
||||
"Time spent within `candidate_validation::validate_candidate_exhaustive`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
};
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
use assert_matches::assert_matches;
|
||||
use futures::executor;
|
||||
use polkadot_node_subsystem::messages::AllMessages;
|
||||
use polkadot_node_subsystem_test_helpers as test_helpers;
|
||||
use polkadot_primitives::v1::{HeadData, UpwardMessage};
|
||||
use sp_core::testing::TaskExecutor;
|
||||
use futures::executor;
|
||||
use assert_matches::assert_matches;
|
||||
use sp_keyring::Sr25519Keyring;
|
||||
|
||||
fn collator_sign(descriptor: &mut CandidateDescriptor, collator: Sr25519Keyring) {
|
||||
@@ -54,11 +54,9 @@ fn correctly_checks_included_assumption() {
|
||||
let pool = TaskExecutor::new();
|
||||
let (mut ctx, mut ctx_handle) = test_helpers::make_subsystem_context(pool.clone());
|
||||
|
||||
let (check_fut, check_result) = check_assumption_validation_data(
|
||||
&mut ctx,
|
||||
&candidate,
|
||||
OccupiedCoreAssumption::Included,
|
||||
).remote_handle();
|
||||
let (check_fut, check_result) =
|
||||
check_assumption_validation_data(&mut ctx, &candidate, OccupiedCoreAssumption::Included)
|
||||
.remote_handle();
|
||||
|
||||
let test_fut = async move {
|
||||
assert_matches!(
|
||||
@@ -118,11 +116,9 @@ fn correctly_checks_timed_out_assumption() {
|
||||
let pool = TaskExecutor::new();
|
||||
let (mut ctx, mut ctx_handle) = test_helpers::make_subsystem_context(pool.clone());
|
||||
|
||||
let (check_fut, check_result) = check_assumption_validation_data(
|
||||
&mut ctx,
|
||||
&candidate,
|
||||
OccupiedCoreAssumption::TimedOut,
|
||||
).remote_handle();
|
||||
let (check_fut, check_result) =
|
||||
check_assumption_validation_data(&mut ctx, &candidate, OccupiedCoreAssumption::TimedOut)
|
||||
.remote_handle();
|
||||
|
||||
let test_fut = async move {
|
||||
assert_matches!(
|
||||
@@ -180,11 +176,9 @@ fn check_is_bad_request_if_no_validation_data() {
|
||||
let pool = TaskExecutor::new();
|
||||
let (mut ctx, mut ctx_handle) = test_helpers::make_subsystem_context(pool.clone());
|
||||
|
||||
let (check_fut, check_result) = check_assumption_validation_data(
|
||||
&mut ctx,
|
||||
&candidate,
|
||||
OccupiedCoreAssumption::Included,
|
||||
).remote_handle();
|
||||
let (check_fut, check_result) =
|
||||
check_assumption_validation_data(&mut ctx, &candidate, OccupiedCoreAssumption::Included)
|
||||
.remote_handle();
|
||||
|
||||
let test_fut = async move {
|
||||
assert_matches!(
|
||||
@@ -226,11 +220,9 @@ fn check_is_bad_request_if_no_validation_code() {
|
||||
let pool = TaskExecutor::new();
|
||||
let (mut ctx, mut ctx_handle) = test_helpers::make_subsystem_context(pool.clone());
|
||||
|
||||
let (check_fut, check_result) = check_assumption_validation_data(
|
||||
&mut ctx,
|
||||
&candidate,
|
||||
OccupiedCoreAssumption::TimedOut,
|
||||
).remote_handle();
|
||||
let (check_fut, check_result) =
|
||||
check_assumption_validation_data(&mut ctx, &candidate, OccupiedCoreAssumption::TimedOut)
|
||||
.remote_handle();
|
||||
|
||||
let test_fut = async move {
|
||||
assert_matches!(
|
||||
@@ -284,11 +276,9 @@ fn check_does_not_match() {
|
||||
let pool = TaskExecutor::new();
|
||||
let (mut ctx, mut ctx_handle) = test_helpers::make_subsystem_context(pool.clone());
|
||||
|
||||
let (check_fut, check_result) = check_assumption_validation_data(
|
||||
&mut ctx,
|
||||
&candidate,
|
||||
OccupiedCoreAssumption::Included,
|
||||
).remote_handle();
|
||||
let (check_fut, check_result) =
|
||||
check_assumption_validation_data(&mut ctx, &candidate, OccupiedCoreAssumption::Included)
|
||||
.remote_handle();
|
||||
|
||||
let test_fut = async move {
|
||||
assert_matches!(
|
||||
@@ -321,9 +311,7 @@ struct MockValidatorBackend {
|
||||
|
||||
impl MockValidatorBackend {
|
||||
fn with_hardcoded_result(result: Result<WasmValidationResult, ValidationError>) -> Self {
|
||||
Self {
|
||||
result,
|
||||
}
|
||||
Self { result }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,7 +320,7 @@ impl ValidationBackend for MockValidatorBackend {
|
||||
async fn validate_candidate(
|
||||
&mut self,
|
||||
_raw_validation_code: Vec<u8>,
|
||||
_params: ValidationParams
|
||||
_params: ValidationParams,
|
||||
) -> Result<WasmValidationResult, ValidationError> {
|
||||
self.result.clone()
|
||||
}
|
||||
@@ -352,12 +340,8 @@ fn candidate_validation_ok_is_ok() {
|
||||
descriptor.validation_code_hash = validation_code.hash();
|
||||
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
||||
|
||||
let check = perform_basic_checks(
|
||||
&descriptor,
|
||||
validation_data.max_pov_size,
|
||||
&pov,
|
||||
&validation_code,
|
||||
);
|
||||
let check =
|
||||
perform_basic_checks(&descriptor, validation_data.max_pov_size, &pov, &validation_code);
|
||||
assert!(check.is_ok());
|
||||
|
||||
let validation_result = WasmValidationResult {
|
||||
@@ -402,18 +386,14 @@ fn candidate_validation_bad_return_is_invalid() {
|
||||
descriptor.validation_code_hash = validation_code.hash();
|
||||
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
||||
|
||||
let check = perform_basic_checks(
|
||||
&descriptor,
|
||||
validation_data.max_pov_size,
|
||||
&pov,
|
||||
&validation_code,
|
||||
);
|
||||
let check =
|
||||
perform_basic_checks(&descriptor, validation_data.max_pov_size, &pov, &validation_code);
|
||||
assert!(check.is_ok());
|
||||
|
||||
let v = executor::block_on(validate_candidate_exhaustive(
|
||||
MockValidatorBackend::with_hardcoded_result(
|
||||
Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::AmbigiousWorkerDeath))
|
||||
),
|
||||
MockValidatorBackend::with_hardcoded_result(Err(ValidationError::InvalidCandidate(
|
||||
WasmInvalidCandidate::AmbigiousWorkerDeath,
|
||||
))),
|
||||
validation_data,
|
||||
validation_code,
|
||||
descriptor,
|
||||
@@ -438,18 +418,14 @@ fn candidate_validation_timeout_is_internal_error() {
|
||||
descriptor.validation_code_hash = validation_code.hash();
|
||||
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
||||
|
||||
let check = perform_basic_checks(
|
||||
&descriptor,
|
||||
validation_data.max_pov_size,
|
||||
&pov,
|
||||
&validation_code,
|
||||
);
|
||||
let check =
|
||||
perform_basic_checks(&descriptor, validation_data.max_pov_size, &pov, &validation_code);
|
||||
assert!(check.is_ok());
|
||||
|
||||
let v = executor::block_on(validate_candidate_exhaustive(
|
||||
MockValidatorBackend::with_hardcoded_result(
|
||||
Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::HardTimeout)),
|
||||
),
|
||||
MockValidatorBackend::with_hardcoded_result(Err(ValidationError::InvalidCandidate(
|
||||
WasmInvalidCandidate::HardTimeout,
|
||||
))),
|
||||
validation_data,
|
||||
validation_code,
|
||||
descriptor,
|
||||
@@ -473,18 +449,14 @@ fn candidate_validation_code_mismatch_is_invalid() {
|
||||
descriptor.validation_code_hash = ValidationCode(vec![1; 16]).hash();
|
||||
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
||||
|
||||
let check = perform_basic_checks(
|
||||
&descriptor,
|
||||
validation_data.max_pov_size,
|
||||
&pov,
|
||||
&validation_code,
|
||||
);
|
||||
let check =
|
||||
perform_basic_checks(&descriptor, validation_data.max_pov_size, &pov, &validation_code);
|
||||
assert_matches!(check, Err(InvalidCandidate::CodeHashMismatch));
|
||||
|
||||
let v = executor::block_on(validate_candidate_exhaustive(
|
||||
MockValidatorBackend::with_hardcoded_result(
|
||||
Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::HardTimeout)),
|
||||
),
|
||||
MockValidatorBackend::with_hardcoded_result(Err(ValidationError::InvalidCandidate(
|
||||
WasmInvalidCandidate::HardTimeout,
|
||||
))),
|
||||
validation_data,
|
||||
validation_code,
|
||||
descriptor,
|
||||
@@ -504,10 +476,7 @@ fn compressed_code_works() {
|
||||
let head_data = HeadData(vec![1, 1, 1]);
|
||||
|
||||
let raw_code = vec![2u8; 16];
|
||||
let validation_code = sp_maybe_compressed_blob::compress(
|
||||
&raw_code,
|
||||
VALIDATION_CODE_BOMB_LIMIT,
|
||||
)
|
||||
let validation_code = sp_maybe_compressed_blob::compress(&raw_code, VALIDATION_CODE_BOMB_LIMIT)
|
||||
.map(ValidationCode)
|
||||
.unwrap();
|
||||
|
||||
@@ -546,12 +515,10 @@ fn code_decompression_failure_is_invalid() {
|
||||
let head_data = HeadData(vec![1, 1, 1]);
|
||||
|
||||
let raw_code = vec![2u8; VALIDATION_CODE_BOMB_LIMIT + 1];
|
||||
let validation_code = sp_maybe_compressed_blob::compress(
|
||||
&raw_code,
|
||||
VALIDATION_CODE_BOMB_LIMIT + 1,
|
||||
)
|
||||
.map(ValidationCode)
|
||||
.unwrap();
|
||||
let validation_code =
|
||||
sp_maybe_compressed_blob::compress(&raw_code, VALIDATION_CODE_BOMB_LIMIT + 1)
|
||||
.map(ValidationCode)
|
||||
.unwrap();
|
||||
|
||||
let mut descriptor = CandidateDescriptor::default();
|
||||
descriptor.pov_hash = pov.hash();
|
||||
@@ -578,25 +545,17 @@ fn code_decompression_failure_is_invalid() {
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
assert_matches!(
|
||||
v,
|
||||
Ok(ValidationResult::Invalid(InvalidCandidate::CodeDecompressionFailure))
|
||||
);
|
||||
assert_matches!(v, Ok(ValidationResult::Invalid(InvalidCandidate::CodeDecompressionFailure)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pov_decompression_failure_is_invalid() {
|
||||
let validation_data = PersistedValidationData {
|
||||
max_pov_size: POV_BOMB_LIMIT as u32,
|
||||
..Default::default()
|
||||
};
|
||||
let validation_data =
|
||||
PersistedValidationData { max_pov_size: POV_BOMB_LIMIT as u32, ..Default::default() };
|
||||
let head_data = HeadData(vec![1, 1, 1]);
|
||||
|
||||
let raw_block_data = vec![2u8; POV_BOMB_LIMIT + 1];
|
||||
let pov = sp_maybe_compressed_blob::compress(
|
||||
&raw_block_data,
|
||||
POV_BOMB_LIMIT + 1,
|
||||
)
|
||||
let pov = sp_maybe_compressed_blob::compress(&raw_block_data, POV_BOMB_LIMIT + 1)
|
||||
.map(|raw| PoV { block_data: BlockData(raw) })
|
||||
.unwrap();
|
||||
|
||||
@@ -627,8 +586,5 @@ fn pov_decompression_failure_is_invalid() {
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
assert_matches!(
|
||||
v,
|
||||
Ok(ValidationResult::Invalid(InvalidCandidate::PoVDecompressionFailure))
|
||||
);
|
||||
assert_matches!(v, Ok(ValidationResult::Invalid(InvalidCandidate::PoVDecompressionFailure)));
|
||||
}
|
||||
|
||||
@@ -40,9 +40,7 @@ use sp_blockchain::HeaderBackend;
|
||||
use polkadot_node_subsystem_util::metrics::{self, prometheus};
|
||||
use polkadot_primitives::v1::{Block, BlockId};
|
||||
use polkadot_subsystem::{
|
||||
overseer,
|
||||
messages::ChainApiMessage,
|
||||
FromOverseer, OverseerSignal, SpawnedSubsystem,
|
||||
messages::ChainApiMessage, overseer, FromOverseer, OverseerSignal, SpawnedSubsystem,
|
||||
SubsystemContext, SubsystemError, SubsystemResult,
|
||||
};
|
||||
|
||||
@@ -60,10 +58,7 @@ pub struct ChainApiSubsystem<Client> {
|
||||
impl<Client> ChainApiSubsystem<Client> {
|
||||
/// Create a new Chain API subsystem with the given client.
|
||||
pub fn new(client: Arc<Client>, metrics: Metrics) -> Self {
|
||||
ChainApiSubsystem {
|
||||
client,
|
||||
metrics,
|
||||
}
|
||||
ChainApiSubsystem { client, metrics }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,10 +72,7 @@ where
|
||||
let future = run::<Client, Context>(ctx, self)
|
||||
.map_err(|e| SubsystemError::with_origin("chain-api", e))
|
||||
.boxed();
|
||||
SpawnedSubsystem {
|
||||
future,
|
||||
name: "chain-api-subsystem",
|
||||
}
|
||||
SpawnedSubsystem { future, name: "chain-api-subsystem" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +99,8 @@ where
|
||||
},
|
||||
ChainApiMessage::BlockHeader(hash, response_channel) => {
|
||||
let _timer = subsystem.metrics.time_block_header();
|
||||
let result = subsystem.client
|
||||
let result = subsystem
|
||||
.client
|
||||
.header(BlockId::Hash(hash))
|
||||
.map_err(|e| e.to_string().into());
|
||||
subsystem.metrics.on_request(result.is_ok());
|
||||
@@ -119,7 +112,7 @@ where
|
||||
.map_err(|e| e.to_string().into());
|
||||
subsystem.metrics.on_request(result.is_ok());
|
||||
let _ = response_channel.send(result);
|
||||
}
|
||||
},
|
||||
ChainApiMessage::FinalizedBlockHash(number, response_channel) => {
|
||||
let _timer = subsystem.metrics.time_finalized_block_hash();
|
||||
// Note: we don't verify it's finalized
|
||||
@@ -158,7 +151,7 @@ where
|
||||
hash = header.parent_hash;
|
||||
Some(Ok(hash))
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
@@ -166,7 +159,7 @@ where
|
||||
subsystem.metrics.on_request(result.is_ok());
|
||||
let _ = response_channel.send(result);
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,7 +211,9 @@ impl Metrics {
|
||||
}
|
||||
|
||||
/// Provide a timer for `finalized_block_number` which observes on drop.
|
||||
fn time_finalized_block_number(&self) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
|
||||
fn time_finalized_block_number(
|
||||
&self,
|
||||
) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
|
||||
self.0.as_ref().map(|metrics| metrics.finalized_block_number.start_timer())
|
||||
}
|
||||
|
||||
@@ -242,57 +237,45 @@ impl metrics::Metrics for Metrics {
|
||||
registry,
|
||||
)?,
|
||||
block_number: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_chain_api_block_number",
|
||||
"Time spent within `chain_api::block_number`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_chain_api_block_number",
|
||||
"Time spent within `chain_api::block_number`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
block_header: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_chain_api_block_headers",
|
||||
"Time spent within `chain_api::block_headers`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_chain_api_block_headers",
|
||||
"Time spent within `chain_api::block_headers`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
block_weight: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_chain_api_block_weight",
|
||||
"Time spent within `chain_api::block_weight`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_chain_api_block_weight",
|
||||
"Time spent within `chain_api::block_weight`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
finalized_block_hash: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_chain_api_finalized_block_hash",
|
||||
"Time spent within `chain_api::finalized_block_hash`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_chain_api_finalized_block_hash",
|
||||
"Time spent within `chain_api::finalized_block_hash`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
finalized_block_number: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_chain_api_finalized_block_number",
|
||||
"Time spent within `chain_api::finalized_block_number`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_chain_api_finalized_block_number",
|
||||
"Time spent within `chain_api::finalized_block_number`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
ancestors: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_chain_api_ancestors",
|
||||
"Time spent within `chain_api::ancestors`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_chain_api_ancestors",
|
||||
"Time spent within `chain_api::ancestors`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
};
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use super::*;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use futures::{future::BoxFuture, channel::oneshot};
|
||||
use futures::{channel::oneshot, future::BoxFuture};
|
||||
use parity_scale_codec::Encode;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use polkadot_primitives::v1::{Hash, BlockNumber, BlockId, Header};
|
||||
use polkadot_node_primitives::BlockWeight;
|
||||
use polkadot_node_subsystem_test_helpers::{make_subsystem_context, TestSubsystemContextHandle};
|
||||
use polkadot_primitives::v1::{BlockId, BlockNumber, Hash, Header};
|
||||
use sp_blockchain::Info as BlockInfo;
|
||||
use sp_core::testing::TaskExecutor;
|
||||
|
||||
@@ -79,10 +79,7 @@ impl Default for TestClient {
|
||||
|
||||
fn last_key_value<K: Clone, V: Clone>(map: &BTreeMap<K, V>) -> (K, V) {
|
||||
assert!(!map.is_empty());
|
||||
map.iter()
|
||||
.last()
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.unwrap()
|
||||
map.iter().last().map(|(k, v)| (k.clone(), v.clone())).unwrap()
|
||||
}
|
||||
|
||||
impl HeaderBackend<Block> for TestClient {
|
||||
@@ -110,12 +107,9 @@ impl HeaderBackend<Block> for TestClient {
|
||||
fn header(&self, id: BlockId) -> sp_blockchain::Result<Option<Header>> {
|
||||
match id {
|
||||
// for error path testing
|
||||
BlockId::Hash(hash) if hash.is_zero() => {
|
||||
Err(sp_blockchain::Error::Backend("Zero hashes are illegal!".into()))
|
||||
}
|
||||
BlockId::Hash(hash) => {
|
||||
Ok(self.headers.get(&hash).cloned())
|
||||
}
|
||||
BlockId::Hash(hash) if hash.is_zero() =>
|
||||
Err(sp_blockchain::Error::Backend("Zero hashes are illegal!".into())),
|
||||
BlockId::Hash(hash) => Ok(self.headers.get(&hash).cloned()),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -125,8 +119,10 @@ impl HeaderBackend<Block> for TestClient {
|
||||
}
|
||||
|
||||
fn test_harness(
|
||||
test: impl FnOnce(Arc<TestClient>, TestSubsystemContextHandle<ChainApiMessage>)
|
||||
-> BoxFuture<'static, ()>,
|
||||
test: impl FnOnce(
|
||||
Arc<TestClient>,
|
||||
TestSubsystemContextHandle<ChainApiMessage>,
|
||||
) -> BoxFuture<'static, ()>,
|
||||
) {
|
||||
let (ctx, ctx_handle) = make_subsystem_context(TaskExecutor::new());
|
||||
let client = Arc::new(TestClient::default());
|
||||
@@ -174,15 +170,18 @@ fn request_block_number() {
|
||||
for (hash, expected) in &test_cases {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
sender.send(FromOverseer::Communication {
|
||||
msg: ChainApiMessage::BlockNumber(*hash, tx),
|
||||
}).await;
|
||||
sender
|
||||
.send(FromOverseer::Communication {
|
||||
msg: ChainApiMessage::BlockNumber(*hash, tx),
|
||||
})
|
||||
.await;
|
||||
|
||||
assert_eq!(rx.await.unwrap().unwrap(), *expected);
|
||||
}
|
||||
|
||||
sender.send(FromOverseer::Signal(OverseerSignal::Conclude)).await;
|
||||
}.boxed()
|
||||
}
|
||||
.boxed()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -198,15 +197,18 @@ fn request_block_header() {
|
||||
for (hash, expected) in &test_cases {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
sender.send(FromOverseer::Communication {
|
||||
msg: ChainApiMessage::BlockHeader(*hash, tx),
|
||||
}).await;
|
||||
sender
|
||||
.send(FromOverseer::Communication {
|
||||
msg: ChainApiMessage::BlockHeader(*hash, tx),
|
||||
})
|
||||
.await;
|
||||
|
||||
assert_eq!(rx.await.unwrap().unwrap(), *expected);
|
||||
}
|
||||
|
||||
sender.send(FromOverseer::Signal(OverseerSignal::Conclude)).await;
|
||||
}.boxed()
|
||||
}
|
||||
.boxed()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -223,15 +225,18 @@ fn request_block_weight() {
|
||||
for (hash, expected) in &test_cases {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
sender.send(FromOverseer::Communication {
|
||||
msg: ChainApiMessage::BlockWeight(*hash, tx),
|
||||
}).await;
|
||||
sender
|
||||
.send(FromOverseer::Communication {
|
||||
msg: ChainApiMessage::BlockWeight(*hash, tx),
|
||||
})
|
||||
.await;
|
||||
|
||||
assert_eq!(rx.await.unwrap().unwrap(), *expected);
|
||||
}
|
||||
|
||||
sender.send(FromOverseer::Signal(OverseerSignal::Conclude)).await;
|
||||
}.boxed()
|
||||
}
|
||||
.boxed()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -246,15 +251,18 @@ fn request_finalized_hash() {
|
||||
for (number, expected) in &test_cases {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
sender.send(FromOverseer::Communication {
|
||||
msg: ChainApiMessage::FinalizedBlockHash(*number, tx),
|
||||
}).await;
|
||||
sender
|
||||
.send(FromOverseer::Communication {
|
||||
msg: ChainApiMessage::FinalizedBlockHash(*number, tx),
|
||||
})
|
||||
.await;
|
||||
|
||||
assert_eq!(rx.await.unwrap().unwrap(), *expected);
|
||||
}
|
||||
|
||||
sender.send(FromOverseer::Signal(OverseerSignal::Conclude)).await;
|
||||
}.boxed()
|
||||
}
|
||||
.boxed()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -265,14 +273,17 @@ fn request_last_finalized_number() {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
let expected = client.info().finalized_number;
|
||||
sender.send(FromOverseer::Communication {
|
||||
msg: ChainApiMessage::FinalizedBlockNumber(tx),
|
||||
}).await;
|
||||
sender
|
||||
.send(FromOverseer::Communication {
|
||||
msg: ChainApiMessage::FinalizedBlockNumber(tx),
|
||||
})
|
||||
.await;
|
||||
|
||||
assert_eq!(rx.await.unwrap().unwrap(), expected);
|
||||
|
||||
sender.send(FromOverseer::Signal(OverseerSignal::Conclude)).await;
|
||||
}.boxed()
|
||||
}
|
||||
.boxed()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -281,24 +292,35 @@ fn request_ancestors() {
|
||||
test_harness(|_client, mut sender| {
|
||||
async move {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
sender.send(FromOverseer::Communication {
|
||||
msg: ChainApiMessage::Ancestors { hash: THREE, k: 4, response_channel: tx },
|
||||
}).await;
|
||||
sender
|
||||
.send(FromOverseer::Communication {
|
||||
msg: ChainApiMessage::Ancestors { hash: THREE, k: 4, response_channel: tx },
|
||||
})
|
||||
.await;
|
||||
assert_eq!(rx.await.unwrap().unwrap(), vec![TWO, ONE]);
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
sender.send(FromOverseer::Communication {
|
||||
msg: ChainApiMessage::Ancestors { hash: TWO, k: 1, response_channel: tx },
|
||||
}).await;
|
||||
sender
|
||||
.send(FromOverseer::Communication {
|
||||
msg: ChainApiMessage::Ancestors { hash: TWO, k: 1, response_channel: tx },
|
||||
})
|
||||
.await;
|
||||
assert_eq!(rx.await.unwrap().unwrap(), vec![ONE]);
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
sender.send(FromOverseer::Communication {
|
||||
msg: ChainApiMessage::Ancestors { hash: ERROR_PATH, k: 2, response_channel: tx },
|
||||
}).await;
|
||||
sender
|
||||
.send(FromOverseer::Communication {
|
||||
msg: ChainApiMessage::Ancestors {
|
||||
hash: ERROR_PATH,
|
||||
k: 2,
|
||||
response_channel: tx,
|
||||
},
|
||||
})
|
||||
.await;
|
||||
assert!(rx.await.unwrap().is_err());
|
||||
|
||||
sender.send(FromOverseer::Signal(OverseerSignal::Conclude)).await;
|
||||
}.boxed()
|
||||
}
|
||||
.boxed()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ use polkadot_primitives::v1::{BlockNumber, Hash};
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{Error, LeafEntrySet, BlockEntry, Timestamp};
|
||||
use crate::{BlockEntry, Error, LeafEntrySet, Timestamp};
|
||||
|
||||
pub(super) enum BackendWriteOp {
|
||||
WriteBlockEntry(BlockEntry),
|
||||
@@ -47,8 +47,10 @@ pub(super) trait Backend {
|
||||
fn load_stagnant_at(&self, timestamp: Timestamp) -> Result<Vec<Hash>, Error>;
|
||||
/// Load all stagnant lists up to and including the given Unix timestamp
|
||||
/// in ascending order.
|
||||
fn load_stagnant_at_up_to(&self, up_to: Timestamp)
|
||||
-> Result<Vec<(Timestamp, Vec<Hash>)>, Error>;
|
||||
fn load_stagnant_at_up_to(
|
||||
&self,
|
||||
up_to: Timestamp,
|
||||
) -> Result<Vec<(Timestamp, Vec<Hash>)>, Error>;
|
||||
/// Load the earliest kept block number.
|
||||
fn load_first_block_number(&self) -> Result<Option<BlockNumber>, Error>;
|
||||
/// Load blocks by number.
|
||||
@@ -56,7 +58,8 @@ pub(super) trait Backend {
|
||||
|
||||
/// Atomically write the list of operations, with later operations taking precedence over prior.
|
||||
fn write<I>(&mut self, ops: I) -> Result<(), Error>
|
||||
where I: IntoIterator<Item = BackendWriteOp>;
|
||||
where
|
||||
I: IntoIterator<Item = BackendWriteOp>;
|
||||
}
|
||||
|
||||
/// An in-memory overlay over the backend.
|
||||
@@ -98,7 +101,7 @@ impl<'a, B: 'a + Backend> OverlayedBackend<'a, B> {
|
||||
|
||||
pub(super) fn load_blocks_by_number(&self, number: BlockNumber) -> Result<Vec<Hash>, Error> {
|
||||
if let Some(val) = self.blocks_by_number.get(&number) {
|
||||
return Ok(val.as_ref().map_or(Vec::new(), Clone::clone));
|
||||
return Ok(val.as_ref().map_or(Vec::new(), Clone::clone))
|
||||
}
|
||||
|
||||
self.inner.load_blocks_by_number(number)
|
||||
@@ -114,7 +117,7 @@ impl<'a, B: 'a + Backend> OverlayedBackend<'a, B> {
|
||||
|
||||
pub(super) fn load_stagnant_at(&self, timestamp: Timestamp) -> Result<Vec<Hash>, Error> {
|
||||
if let Some(val) = self.stagnant_at.get(×tamp) {
|
||||
return Ok(val.as_ref().map_or(Vec::new(), Clone::clone));
|
||||
return Ok(val.as_ref().map_or(Vec::new(), Clone::clone))
|
||||
}
|
||||
|
||||
self.inner.load_stagnant_at(timestamp)
|
||||
@@ -188,17 +191,15 @@ impl<'a, B: 'a + Backend> OverlayedBackend<'a, B> {
|
||||
/// return true if `ancestor` is in `head`'s chain.
|
||||
///
|
||||
/// If the ancestor is an older finalized block, this will return `false`.
|
||||
fn contains_ancestor(
|
||||
backend: &impl Backend,
|
||||
head: Hash,
|
||||
ancestor: Hash,
|
||||
) -> Result<bool, Error> {
|
||||
fn contains_ancestor(backend: &impl Backend, head: Hash, ancestor: Hash) -> Result<bool, Error> {
|
||||
let mut current_hash = head;
|
||||
loop {
|
||||
if current_hash == ancestor { return Ok(true) }
|
||||
if current_hash == ancestor {
|
||||
return Ok(true)
|
||||
}
|
||||
match backend.load_block_entry(¤t_hash)? {
|
||||
Some(e) => { current_hash = e.parent_hash }
|
||||
None => break
|
||||
Some(e) => current_hash = e.parent_hash,
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,14 +32,16 @@
|
||||
//! The `Vec`s stored are always non-empty. Empty `Vec`s are not stored on disk so there is no
|
||||
//! semantic difference between `None` and an empty `Vec`.
|
||||
|
||||
use crate::backend::{Backend, BackendWriteOp};
|
||||
use crate::Error;
|
||||
use crate::{
|
||||
backend::{Backend, BackendWriteOp},
|
||||
Error,
|
||||
};
|
||||
|
||||
use polkadot_primitives::v1::{BlockNumber, Hash};
|
||||
use polkadot_node_primitives::BlockWeight;
|
||||
use polkadot_primitives::v1::{BlockNumber, Hash};
|
||||
|
||||
use kvdb::{DBTransaction, KeyValueDB};
|
||||
use parity_scale_codec::{Encode, Decode};
|
||||
use parity_scale_codec::{Decode, Encode};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -116,11 +118,7 @@ struct LeafEntry {
|
||||
|
||||
impl From<crate::LeafEntry> for LeafEntry {
|
||||
fn from(x: crate::LeafEntry) -> Self {
|
||||
LeafEntry {
|
||||
weight: x.weight,
|
||||
block_number: x.block_number,
|
||||
block_hash: x.block_hash,
|
||||
}
|
||||
LeafEntry { weight: x.weight, block_number: x.block_number, block_hash: x.block_hash }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,17 +139,13 @@ struct LeafEntrySet {
|
||||
|
||||
impl From<crate::LeafEntrySet> for LeafEntrySet {
|
||||
fn from(x: crate::LeafEntrySet) -> Self {
|
||||
LeafEntrySet {
|
||||
inner: x.inner.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
LeafEntrySet { inner: x.inner.into_iter().map(Into::into).collect() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LeafEntrySet> for crate::LeafEntrySet {
|
||||
fn from(x: LeafEntrySet) -> crate::LeafEntrySet {
|
||||
crate::LeafEntrySet {
|
||||
inner: x.inner.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
crate::LeafEntrySet { inner: x.inner.into_iter().map(Into::into).collect() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,28 +202,19 @@ impl DbBackend {
|
||||
/// Create a new [`DbBackend`] with the supplied key-value store and
|
||||
/// config.
|
||||
pub fn new(db: Arc<dyn KeyValueDB>, config: Config) -> Self {
|
||||
DbBackend {
|
||||
inner: db,
|
||||
config,
|
||||
}
|
||||
DbBackend { inner: db, config }
|
||||
}
|
||||
}
|
||||
|
||||
impl Backend for DbBackend {
|
||||
fn load_block_entry(&self, hash: &Hash) -> Result<Option<crate::BlockEntry>, Error> {
|
||||
load_decode::<BlockEntry>(
|
||||
&*self.inner,
|
||||
self.config.col_data,
|
||||
&block_entry_key(hash),
|
||||
).map(|o| o.map(Into::into))
|
||||
load_decode::<BlockEntry>(&*self.inner, self.config.col_data, &block_entry_key(hash))
|
||||
.map(|o| o.map(Into::into))
|
||||
}
|
||||
|
||||
fn load_leaves(&self) -> Result<crate::LeafEntrySet, Error> {
|
||||
load_decode::<LeafEntrySet>(
|
||||
&*self.inner,
|
||||
self.config.col_data,
|
||||
LEAVES_KEY,
|
||||
).map(|o| o.map(Into::into).unwrap_or_default())
|
||||
load_decode::<LeafEntrySet>(&*self.inner, self.config.col_data, LEAVES_KEY)
|
||||
.map(|o| o.map(Into::into).unwrap_or_default())
|
||||
}
|
||||
|
||||
fn load_stagnant_at(&self, timestamp: crate::Timestamp) -> Result<Vec<Hash>, Error> {
|
||||
@@ -237,16 +222,16 @@ impl Backend for DbBackend {
|
||||
&*self.inner,
|
||||
self.config.col_data,
|
||||
&stagnant_at_key(timestamp.into()),
|
||||
).map(|o| o.unwrap_or_default())
|
||||
)
|
||||
.map(|o| o.unwrap_or_default())
|
||||
}
|
||||
|
||||
fn load_stagnant_at_up_to(&self, up_to: crate::Timestamp)
|
||||
-> Result<Vec<(crate::Timestamp, Vec<Hash>)>, Error>
|
||||
{
|
||||
let stagnant_at_iter = self.inner.iter_with_prefix(
|
||||
self.config.col_data,
|
||||
&STAGNANT_AT_PREFIX[..],
|
||||
);
|
||||
fn load_stagnant_at_up_to(
|
||||
&self,
|
||||
up_to: crate::Timestamp,
|
||||
) -> Result<Vec<(crate::Timestamp, Vec<Hash>)>, Error> {
|
||||
let stagnant_at_iter =
|
||||
self.inner.iter_with_prefix(self.config.col_data, &STAGNANT_AT_PREFIX[..]);
|
||||
|
||||
let val = stagnant_at_iter
|
||||
.filter_map(|(k, v)| {
|
||||
@@ -262,10 +247,8 @@ impl Backend for DbBackend {
|
||||
}
|
||||
|
||||
fn load_first_block_number(&self) -> Result<Option<BlockNumber>, Error> {
|
||||
let blocks_at_height_iter = self.inner.iter_with_prefix(
|
||||
self.config.col_data,
|
||||
&BLOCK_HEIGHT_PREFIX[..],
|
||||
);
|
||||
let blocks_at_height_iter =
|
||||
self.inner.iter_with_prefix(self.config.col_data, &BLOCK_HEIGHT_PREFIX[..]);
|
||||
|
||||
let val = blocks_at_height_iter
|
||||
.filter_map(|(k, _)| decode_block_height_key(&k[..]))
|
||||
@@ -275,16 +258,14 @@ impl Backend for DbBackend {
|
||||
}
|
||||
|
||||
fn load_blocks_by_number(&self, number: BlockNumber) -> Result<Vec<Hash>, Error> {
|
||||
load_decode::<Vec<Hash>>(
|
||||
&*self.inner,
|
||||
self.config.col_data,
|
||||
&block_height_key(number),
|
||||
).map(|o| o.unwrap_or_default())
|
||||
load_decode::<Vec<Hash>>(&*self.inner, self.config.col_data, &block_height_key(number))
|
||||
.map(|o| o.unwrap_or_default())
|
||||
}
|
||||
|
||||
/// Atomically write the list of operations, with later operations taking precedence over prior.
|
||||
fn write<I>(&mut self, ops: I) -> Result<(), Error>
|
||||
where I: IntoIterator<Item = BackendWriteOp>
|
||||
where
|
||||
I: IntoIterator<Item = BackendWriteOp>,
|
||||
{
|
||||
let mut tx = DBTransaction::new();
|
||||
for op in ops {
|
||||
@@ -296,43 +277,29 @@ impl Backend for DbBackend {
|
||||
&block_entry_key(&block_entry.block_hash),
|
||||
block_entry.encode(),
|
||||
);
|
||||
}
|
||||
BackendWriteOp::WriteBlocksByNumber(block_number, v) => {
|
||||
},
|
||||
BackendWriteOp::WriteBlocksByNumber(block_number, v) =>
|
||||
if v.is_empty() {
|
||||
tx.delete(
|
||||
self.config.col_data,
|
||||
&block_height_key(block_number),
|
||||
);
|
||||
tx.delete(self.config.col_data, &block_height_key(block_number));
|
||||
} else {
|
||||
tx.put_vec(
|
||||
self.config.col_data,
|
||||
&block_height_key(block_number),
|
||||
v.encode(),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
BackendWriteOp::WriteViableLeaves(leaves) => {
|
||||
let leaves: LeafEntrySet = leaves.into();
|
||||
if leaves.inner.is_empty() {
|
||||
tx.delete(
|
||||
self.config.col_data,
|
||||
&LEAVES_KEY[..],
|
||||
);
|
||||
tx.delete(self.config.col_data, &LEAVES_KEY[..]);
|
||||
} else {
|
||||
tx.put_vec(
|
||||
self.config.col_data,
|
||||
&LEAVES_KEY[..],
|
||||
leaves.encode(),
|
||||
);
|
||||
tx.put_vec(self.config.col_data, &LEAVES_KEY[..], leaves.encode());
|
||||
}
|
||||
}
|
||||
},
|
||||
BackendWriteOp::WriteStagnantAt(timestamp, stagnant_at) => {
|
||||
let timestamp: Timestamp = timestamp.into();
|
||||
if stagnant_at.is_empty() {
|
||||
tx.delete(
|
||||
self.config.col_data,
|
||||
&stagnant_at_key(timestamp),
|
||||
);
|
||||
tx.delete(self.config.col_data, &stagnant_at_key(timestamp));
|
||||
} else {
|
||||
tx.put_vec(
|
||||
self.config.col_data,
|
||||
@@ -340,26 +307,17 @@ impl Backend for DbBackend {
|
||||
stagnant_at.encode(),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
BackendWriteOp::DeleteBlocksByNumber(block_number) => {
|
||||
tx.delete(
|
||||
self.config.col_data,
|
||||
&block_height_key(block_number),
|
||||
);
|
||||
}
|
||||
tx.delete(self.config.col_data, &block_height_key(block_number));
|
||||
},
|
||||
BackendWriteOp::DeleteBlockEntry(hash) => {
|
||||
tx.delete(
|
||||
self.config.col_data,
|
||||
&block_entry_key(&hash),
|
||||
);
|
||||
}
|
||||
tx.delete(self.config.col_data, &block_entry_key(&hash));
|
||||
},
|
||||
BackendWriteOp::DeleteStagnantAt(timestamp) => {
|
||||
let timestamp: Timestamp = timestamp.into();
|
||||
tx.delete(
|
||||
self.config.col_data,
|
||||
&stagnant_at_key(timestamp),
|
||||
);
|
||||
}
|
||||
tx.delete(self.config.col_data, &stagnant_at_key(timestamp));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,9 +332,7 @@ fn load_decode<D: Decode>(
|
||||
) -> Result<Option<D>, Error> {
|
||||
match db.get(col_data, key)? {
|
||||
None => Ok(None),
|
||||
Some(raw) => D::decode(&mut &raw[..])
|
||||
.map(Some)
|
||||
.map_err(Into::into),
|
||||
Some(raw) => D::decode(&mut &raw[..]).map(Some).map_err(Into::into),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,8 +358,12 @@ fn stagnant_at_key(timestamp: Timestamp) -> [u8; 14 + 8] {
|
||||
}
|
||||
|
||||
fn decode_block_height_key(key: &[u8]) -> Option<BlockNumber> {
|
||||
if key.len() != 15 + 4 { return None }
|
||||
if !key.starts_with(BLOCK_HEIGHT_PREFIX) { return None }
|
||||
if key.len() != 15 + 4 {
|
||||
return None
|
||||
}
|
||||
if !key.starts_with(BLOCK_HEIGHT_PREFIX) {
|
||||
return None
|
||||
}
|
||||
|
||||
let mut bytes = [0; 4];
|
||||
bytes.copy_from_slice(&key[15..]);
|
||||
@@ -411,8 +371,12 @@ fn decode_block_height_key(key: &[u8]) -> Option<BlockNumber> {
|
||||
}
|
||||
|
||||
fn decode_stagnant_at_key(key: &[u8]) -> Option<Timestamp> {
|
||||
if key.len() != 14 + 8 { return None }
|
||||
if !key.starts_with(STAGNANT_AT_PREFIX) { return None }
|
||||
if key.len() != 14 + 8 {
|
||||
return None
|
||||
}
|
||||
if !key.starts_with(STAGNANT_AT_PREFIX) {
|
||||
return None
|
||||
}
|
||||
|
||||
let mut bytes = [0; 8];
|
||||
bytes.copy_from_slice(&key[14..]);
|
||||
@@ -479,9 +443,9 @@ mod tests {
|
||||
weight: 100,
|
||||
};
|
||||
|
||||
backend.write(vec![
|
||||
BackendWriteOp::WriteBlockEntry(block_entry.clone().into())
|
||||
]).unwrap();
|
||||
backend
|
||||
.write(vec![BackendWriteOp::WriteBlockEntry(block_entry.clone().into())])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
backend.load_block_entry(&block_entry.block_hash).unwrap().map(BlockEntry::from),
|
||||
@@ -509,17 +473,15 @@ mod tests {
|
||||
weight: 100,
|
||||
};
|
||||
|
||||
backend.write(vec![
|
||||
BackendWriteOp::WriteBlockEntry(block_entry.clone().into())
|
||||
]).unwrap();
|
||||
backend
|
||||
.write(vec![BackendWriteOp::WriteBlockEntry(block_entry.clone().into())])
|
||||
.unwrap();
|
||||
|
||||
backend.write(vec![
|
||||
BackendWriteOp::DeleteBlockEntry(block_entry.block_hash),
|
||||
]).unwrap();
|
||||
backend
|
||||
.write(vec![BackendWriteOp::DeleteBlockEntry(block_entry.block_hash)])
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
backend.load_block_entry(&block_entry.block_hash).unwrap().is_none(),
|
||||
);
|
||||
assert!(backend.load_block_entry(&block_entry.block_hash).unwrap().is_none(),);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -529,30 +491,26 @@ mod tests {
|
||||
|
||||
let mut backend = DbBackend::new(db, config);
|
||||
|
||||
assert!(
|
||||
backend.load_first_block_number().unwrap().is_none(),
|
||||
);
|
||||
assert!(backend.load_first_block_number().unwrap().is_none(),);
|
||||
|
||||
backend.write(vec![
|
||||
BackendWriteOp::WriteBlocksByNumber(2, vec![Hash::repeat_byte(0)]),
|
||||
BackendWriteOp::WriteBlocksByNumber(5, vec![Hash::repeat_byte(0)]),
|
||||
BackendWriteOp::WriteBlocksByNumber(10, vec![Hash::repeat_byte(0)]),
|
||||
]).unwrap();
|
||||
backend
|
||||
.write(vec![
|
||||
BackendWriteOp::WriteBlocksByNumber(2, vec![Hash::repeat_byte(0)]),
|
||||
BackendWriteOp::WriteBlocksByNumber(5, vec![Hash::repeat_byte(0)]),
|
||||
BackendWriteOp::WriteBlocksByNumber(10, vec![Hash::repeat_byte(0)]),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
backend.load_first_block_number().unwrap(),
|
||||
Some(2),
|
||||
);
|
||||
assert_eq!(backend.load_first_block_number().unwrap(), Some(2),);
|
||||
|
||||
backend.write(vec![
|
||||
BackendWriteOp::WriteBlocksByNumber(2, vec![]),
|
||||
BackendWriteOp::DeleteBlocksByNumber(5),
|
||||
]).unwrap();
|
||||
backend
|
||||
.write(vec![
|
||||
BackendWriteOp::WriteBlocksByNumber(2, vec![]),
|
||||
BackendWriteOp::DeleteBlocksByNumber(5),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
backend.load_first_block_number().unwrap(),
|
||||
Some(10),
|
||||
);
|
||||
assert_eq!(backend.load_first_block_number().unwrap(), Some(10),);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -563,15 +521,15 @@ mod tests {
|
||||
let mut backend = DbBackend::new(db, config);
|
||||
|
||||
// Prove that it's cheap
|
||||
assert!(
|
||||
backend.load_stagnant_at_up_to(Timestamp::max_value()).unwrap().is_empty(),
|
||||
);
|
||||
assert!(backend.load_stagnant_at_up_to(Timestamp::max_value()).unwrap().is_empty(),);
|
||||
|
||||
backend.write(vec![
|
||||
BackendWriteOp::WriteStagnantAt(2, vec![Hash::repeat_byte(1)]),
|
||||
BackendWriteOp::WriteStagnantAt(5, vec![Hash::repeat_byte(2)]),
|
||||
BackendWriteOp::WriteStagnantAt(10, vec![Hash::repeat_byte(3)]),
|
||||
]).unwrap();
|
||||
backend
|
||||
.write(vec![
|
||||
BackendWriteOp::WriteStagnantAt(2, vec![Hash::repeat_byte(1)]),
|
||||
BackendWriteOp::WriteStagnantAt(5, vec![Hash::repeat_byte(2)]),
|
||||
BackendWriteOp::WriteStagnantAt(10, vec![Hash::repeat_byte(3)]),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
backend.load_stagnant_at_up_to(Timestamp::max_value()).unwrap(),
|
||||
@@ -593,32 +551,21 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
backend.load_stagnant_at_up_to(9).unwrap(),
|
||||
vec![
|
||||
(2, vec![Hash::repeat_byte(1)]),
|
||||
(5, vec![Hash::repeat_byte(2)]),
|
||||
]
|
||||
vec![(2, vec![Hash::repeat_byte(1)]), (5, vec![Hash::repeat_byte(2)]),]
|
||||
);
|
||||
|
||||
backend.write(vec![
|
||||
BackendWriteOp::DeleteStagnantAt(2),
|
||||
]).unwrap();
|
||||
backend.write(vec![BackendWriteOp::DeleteStagnantAt(2)]).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
backend.load_stagnant_at_up_to(5).unwrap(),
|
||||
vec![
|
||||
(5, vec![Hash::repeat_byte(2)]),
|
||||
]
|
||||
vec![(5, vec![Hash::repeat_byte(2)]),]
|
||||
);
|
||||
|
||||
backend.write(vec![
|
||||
BackendWriteOp::WriteStagnantAt(5, vec![]),
|
||||
]).unwrap();
|
||||
backend.write(vec![BackendWriteOp::WriteStagnantAt(5, vec![])]).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
backend.load_stagnant_at_up_to(10).unwrap(),
|
||||
vec![
|
||||
(10, vec![Hash::repeat_byte(3)]),
|
||||
]
|
||||
vec![(10, vec![Hash::repeat_byte(3)]),]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -629,40 +576,29 @@ mod tests {
|
||||
|
||||
let mut backend = DbBackend::new(db, config);
|
||||
|
||||
backend.write(vec![
|
||||
BackendWriteOp::WriteBlocksByNumber(2, vec![Hash::repeat_byte(1)]),
|
||||
BackendWriteOp::WriteBlocksByNumber(5, vec![Hash::repeat_byte(2)]),
|
||||
BackendWriteOp::WriteBlocksByNumber(10, vec![Hash::repeat_byte(3)]),
|
||||
]).unwrap();
|
||||
backend
|
||||
.write(vec![
|
||||
BackendWriteOp::WriteBlocksByNumber(2, vec![Hash::repeat_byte(1)]),
|
||||
BackendWriteOp::WriteBlocksByNumber(5, vec![Hash::repeat_byte(2)]),
|
||||
BackendWriteOp::WriteBlocksByNumber(10, vec![Hash::repeat_byte(3)]),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
backend.load_blocks_by_number(2).unwrap(),
|
||||
vec![Hash::repeat_byte(1)],
|
||||
);
|
||||
assert_eq!(backend.load_blocks_by_number(2).unwrap(), vec![Hash::repeat_byte(1)],);
|
||||
|
||||
assert_eq!(
|
||||
backend.load_blocks_by_number(3).unwrap(),
|
||||
vec![],
|
||||
);
|
||||
assert_eq!(backend.load_blocks_by_number(3).unwrap(), vec![],);
|
||||
|
||||
backend.write(vec![
|
||||
BackendWriteOp::WriteBlocksByNumber(2, vec![]),
|
||||
BackendWriteOp::DeleteBlocksByNumber(5),
|
||||
]).unwrap();
|
||||
backend
|
||||
.write(vec![
|
||||
BackendWriteOp::WriteBlocksByNumber(2, vec![]),
|
||||
BackendWriteOp::DeleteBlocksByNumber(5),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
backend.load_blocks_by_number(2).unwrap(),
|
||||
vec![],
|
||||
);
|
||||
assert_eq!(backend.load_blocks_by_number(2).unwrap(), vec![],);
|
||||
|
||||
assert_eq!(
|
||||
backend.load_blocks_by_number(5).unwrap(),
|
||||
vec![],
|
||||
);
|
||||
assert_eq!(backend.load_blocks_by_number(5).unwrap(), vec![],);
|
||||
|
||||
assert_eq!(
|
||||
backend.load_blocks_by_number(10).unwrap(),
|
||||
vec![Hash::repeat_byte(3)],
|
||||
);
|
||||
assert_eq!(backend.load_blocks_by_number(10).unwrap(), vec![Hash::repeat_byte(3)],);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,25 +16,24 @@
|
||||
|
||||
//! Implements the Chain Selection Subsystem.
|
||||
|
||||
use polkadot_primitives::v1::{BlockNumber, Hash, Header, ConsensusLog};
|
||||
use polkadot_node_primitives::BlockWeight;
|
||||
use polkadot_node_subsystem::{
|
||||
overseer, SubsystemContext, SubsystemError, SpawnedSubsystem,
|
||||
OverseerSignal, FromOverseer,
|
||||
messages::{ChainSelectionMessage, ChainApiMessage},
|
||||
errors::ChainApiError,
|
||||
messages::{ChainApiMessage, ChainSelectionMessage},
|
||||
overseer, FromOverseer, OverseerSignal, SpawnedSubsystem, SubsystemContext, SubsystemError,
|
||||
};
|
||||
use polkadot_primitives::v1::{BlockNumber, ConsensusLog, Hash, Header};
|
||||
|
||||
use futures::{channel::oneshot, future::Either, prelude::*};
|
||||
use kvdb::KeyValueDB;
|
||||
use parity_scale_codec::Error as CodecError;
|
||||
use futures::channel::oneshot;
|
||||
use futures::future::Either;
|
||||
use futures::prelude::*;
|
||||
|
||||
use std::time::{UNIX_EPOCH, Duration,SystemTime};
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use crate::backend::{Backend, OverlayedBackend, BackendWriteOp};
|
||||
use crate::backend::{Backend, BackendWriteOp, OverlayedBackend};
|
||||
|
||||
mod backend;
|
||||
mod db_backend;
|
||||
@@ -108,16 +107,19 @@ struct LeafEntry {
|
||||
|
||||
impl PartialOrd for LeafEntry {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
let ord = self.weight.cmp(&other.weight)
|
||||
.then(self.block_number.cmp(&other.block_number));
|
||||
let ord = self.weight.cmp(&other.weight).then(self.block_number.cmp(&other.block_number));
|
||||
|
||||
if !matches!(ord, std::cmp::Ordering::Equal) { Some(ord) } else { None }
|
||||
if !matches!(ord, std::cmp::Ordering::Equal) {
|
||||
Some(ord)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
struct LeafEntrySet {
|
||||
inner: Vec<LeafEntry>
|
||||
inner: Vec<LeafEntry>,
|
||||
}
|
||||
|
||||
impl LeafEntrySet {
|
||||
@@ -127,14 +129,16 @@ impl LeafEntrySet {
|
||||
Some(i) => {
|
||||
self.inner.remove(i);
|
||||
true
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn insert(&mut self, new: LeafEntry) {
|
||||
let mut pos = None;
|
||||
for (i, e) in self.inner.iter().enumerate() {
|
||||
if e == &new { return }
|
||||
if e == &new {
|
||||
return
|
||||
}
|
||||
if e < &new {
|
||||
pos = Some(i);
|
||||
break
|
||||
@@ -238,7 +242,7 @@ impl Clock for SystemClock {
|
||||
);
|
||||
|
||||
0
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -309,10 +313,7 @@ impl ChainSelectionSubsystem {
|
||||
/// Create a new instance of the subsystem with the given config
|
||||
/// and key-value store.
|
||||
pub fn new(config: Config, db: Arc<dyn KeyValueDB>) -> Self {
|
||||
ChainSelectionSubsystem {
|
||||
config,
|
||||
db,
|
||||
}
|
||||
ChainSelectionSubsystem { config, db }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,12 +329,7 @@ where
|
||||
);
|
||||
|
||||
SpawnedSubsystem {
|
||||
future: run(
|
||||
ctx,
|
||||
backend,
|
||||
self.config.stagnant_check_interval,
|
||||
Box::new(SystemClock),
|
||||
)
|
||||
future: run(ctx, backend, self.config.stagnant_check_interval, Box::new(SystemClock))
|
||||
.map(Ok)
|
||||
.boxed(),
|
||||
name: "chain-selection-subsystem",
|
||||
@@ -346,31 +342,25 @@ async fn run<Context, B>(
|
||||
mut backend: B,
|
||||
stagnant_check_interval: StagnantCheckInterval,
|
||||
clock: Box<dyn Clock + Send + Sync>,
|
||||
)
|
||||
where
|
||||
Context: SubsystemContext<Message = ChainSelectionMessage>,
|
||||
Context: overseer::SubsystemContext<Message = ChainSelectionMessage>,
|
||||
B: Backend,
|
||||
) where
|
||||
Context: SubsystemContext<Message = ChainSelectionMessage>,
|
||||
Context: overseer::SubsystemContext<Message = ChainSelectionMessage>,
|
||||
B: Backend,
|
||||
{
|
||||
loop {
|
||||
let res = run_iteration(
|
||||
&mut ctx,
|
||||
&mut backend,
|
||||
&stagnant_check_interval,
|
||||
&*clock,
|
||||
).await;
|
||||
let res = run_iteration(&mut ctx, &mut backend, &stagnant_check_interval, &*clock).await;
|
||||
match res {
|
||||
Err(e) => {
|
||||
e.trace();
|
||||
|
||||
if let Error::Subsystem(SubsystemError::Context(_)) = e {
|
||||
break;
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
Ok(()) => {
|
||||
tracing::info!(target: LOG_TARGET, "received `Conclude` signal, exiting");
|
||||
break;
|
||||
}
|
||||
break
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -385,12 +375,11 @@ async fn run_iteration<Context, B>(
|
||||
backend: &mut B,
|
||||
stagnant_check_interval: &StagnantCheckInterval,
|
||||
clock: &(dyn Clock + Sync),
|
||||
)
|
||||
-> Result<(), Error>
|
||||
where
|
||||
Context: SubsystemContext<Message = ChainSelectionMessage>,
|
||||
Context: overseer::SubsystemContext<Message = ChainSelectionMessage>,
|
||||
B: Backend,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
Context: SubsystemContext<Message = ChainSelectionMessage>,
|
||||
Context: overseer::SubsystemContext<Message = ChainSelectionMessage>,
|
||||
B: Backend,
|
||||
{
|
||||
let mut stagnant_check_stream = stagnant_check_interval.timeout_stream();
|
||||
loop {
|
||||
@@ -461,15 +450,11 @@ async fn fetch_finalized(
|
||||
|
||||
match hash_rx.await?? {
|
||||
None => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
number,
|
||||
"Missing hash for finalized block number"
|
||||
);
|
||||
tracing::warn!(target: LOG_TARGET, number, "Missing hash for finalized block number");
|
||||
|
||||
return Ok(None)
|
||||
}
|
||||
Some(h) => Ok(Some((h, number)))
|
||||
},
|
||||
Some(h) => Ok(Some((h, number))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,13 +497,9 @@ async fn handle_active_leaf(
|
||||
|
||||
let header = match fetch_header(ctx, hash).await? {
|
||||
None => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
?hash,
|
||||
"Missing header for new head",
|
||||
);
|
||||
tracing::warn!(target: LOG_TARGET, ?hash, "Missing header for new head",);
|
||||
return Ok(Vec::new())
|
||||
}
|
||||
},
|
||||
Some(h) => h,
|
||||
};
|
||||
|
||||
@@ -528,7 +509,8 @@ async fn handle_active_leaf(
|
||||
hash,
|
||||
&header,
|
||||
lower_bound,
|
||||
).await?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut overlay = OverlayedBackend::new(backend);
|
||||
|
||||
@@ -545,8 +527,8 @@ async fn handle_active_leaf(
|
||||
|
||||
// If we don't know the weight, we can't import the block.
|
||||
// And none of its descendants either.
|
||||
break;
|
||||
}
|
||||
break
|
||||
},
|
||||
Some(w) => w,
|
||||
};
|
||||
|
||||
@@ -570,7 +552,9 @@ async fn handle_active_leaf(
|
||||
// Ignores logs with number >= the block header number.
|
||||
fn extract_reversion_logs(header: &Header) -> Vec<BlockNumber> {
|
||||
let number = header.number;
|
||||
let mut logs = header.digest.logs()
|
||||
let mut logs = header
|
||||
.digest
|
||||
.logs()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, d)| match ConsensusLog::from_digest_item(d) {
|
||||
@@ -584,7 +568,7 @@ fn extract_reversion_logs(header: &Header) -> Vec<BlockNumber> {
|
||||
);
|
||||
|
||||
None
|
||||
}
|
||||
},
|
||||
Ok(Some(ConsensusLog::Revert(b))) if b < number => Some(b),
|
||||
Ok(Some(ConsensusLog::Revert(b))) => {
|
||||
tracing::warn!(
|
||||
@@ -596,7 +580,7 @@ fn extract_reversion_logs(header: &Header) -> Vec<BlockNumber> {
|
||||
);
|
||||
|
||||
None
|
||||
}
|
||||
},
|
||||
Ok(_) => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@@ -612,27 +596,18 @@ fn handle_finalized_block(
|
||||
finalized_hash: Hash,
|
||||
finalized_number: BlockNumber,
|
||||
) -> Result<(), Error> {
|
||||
let ops = crate::tree::finalize_block(
|
||||
&*backend,
|
||||
finalized_hash,
|
||||
finalized_number,
|
||||
)?.into_write_ops();
|
||||
let ops =
|
||||
crate::tree::finalize_block(&*backend, finalized_hash, finalized_number)?.into_write_ops();
|
||||
|
||||
backend.write(ops)
|
||||
}
|
||||
|
||||
// Handle an approved block event.
|
||||
fn handle_approved_block(
|
||||
backend: &mut impl Backend,
|
||||
approved_block: Hash,
|
||||
) -> Result<(), Error> {
|
||||
fn handle_approved_block(backend: &mut impl Backend, approved_block: Hash) -> Result<(), Error> {
|
||||
let ops = {
|
||||
let mut overlay = OverlayedBackend::new(&*backend);
|
||||
|
||||
crate::tree::approve_block(
|
||||
&mut overlay,
|
||||
approved_block,
|
||||
)?;
|
||||
crate::tree::approve_block(&mut overlay, approved_block)?;
|
||||
|
||||
overlay.into_write_ops()
|
||||
};
|
||||
@@ -640,15 +615,9 @@ fn handle_approved_block(
|
||||
backend.write(ops)
|
||||
}
|
||||
|
||||
fn detect_stagnant(
|
||||
backend: &mut impl Backend,
|
||||
now: Timestamp,
|
||||
) -> Result<(), Error> {
|
||||
fn detect_stagnant(backend: &mut impl Backend, now: Timestamp) -> Result<(), Error> {
|
||||
let ops = {
|
||||
let overlay = crate::tree::detect_stagnant(
|
||||
&*backend,
|
||||
now,
|
||||
)?;
|
||||
let overlay = crate::tree::detect_stagnant(&*backend, now)?;
|
||||
|
||||
overlay.into_write_ops()
|
||||
};
|
||||
@@ -662,9 +631,7 @@ async fn load_leaves(
|
||||
ctx: &mut impl SubsystemContext,
|
||||
backend: &impl Backend,
|
||||
) -> Result<Vec<Hash>, Error> {
|
||||
let leaves: Vec<_> = backend.load_leaves()?
|
||||
.into_hashes_descending()
|
||||
.collect();
|
||||
let leaves: Vec<_> = backend.load_leaves()?.into_hashes_descending().collect();
|
||||
|
||||
if leaves.is_empty() {
|
||||
Ok(fetch_finalized(ctx).await?.map_or(Vec::new(), |(h, _)| vec![h]))
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,17 +23,12 @@
|
||||
//! Each direct descendant of the finalized block acts as its own sub-tree,
|
||||
//! and as the finalized block advances, orphaned sub-trees are entirely pruned.
|
||||
|
||||
use polkadot_primitives::v1::{BlockNumber, Hash};
|
||||
use polkadot_node_primitives::BlockWeight;
|
||||
|
||||
use polkadot_primitives::v1::{BlockNumber, Hash};
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::{
|
||||
LOG_TARGET,
|
||||
Approval, BlockEntry, Error, LeafEntry, ViabilityCriteria,
|
||||
Timestamp,
|
||||
};
|
||||
use super::{Approval, BlockEntry, Error, LeafEntry, Timestamp, ViabilityCriteria, LOG_TARGET};
|
||||
use crate::backend::{Backend, OverlayedBackend};
|
||||
|
||||
// A viability update to be applied to a block.
|
||||
@@ -43,10 +38,7 @@ impl ViabilityUpdate {
|
||||
// Apply the viability update to a single block, yielding the updated
|
||||
// block entry along with a vector of children and the updates to apply
|
||||
// to them.
|
||||
fn apply(self, mut entry: BlockEntry) -> (
|
||||
BlockEntry,
|
||||
Vec<(Hash, ViabilityUpdate)>
|
||||
) {
|
||||
fn apply(self, mut entry: BlockEntry) -> (BlockEntry, Vec<(Hash, ViabilityUpdate)>) {
|
||||
// 1. When an ancestor has changed from unviable to viable,
|
||||
// we erase the `earliest_unviable_ancestor` of all descendants
|
||||
// until encountering a explicitly unviable descendant D.
|
||||
@@ -81,7 +73,9 @@ impl ViabilityUpdate {
|
||||
};
|
||||
entry.viability.earliest_unviable_ancestor = maybe_earliest_unviable;
|
||||
|
||||
let recurse = entry.children.iter()
|
||||
let recurse = entry
|
||||
.children
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(move |c| (c, ViabilityUpdate(next_earliest_unviable)))
|
||||
.collect();
|
||||
@@ -152,10 +146,10 @@ fn propagate_viability_update(
|
||||
"Missing expected block entry"
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
continue
|
||||
},
|
||||
Some(entry) => entry,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let (new_entry, children) = update.apply(entry);
|
||||
@@ -184,9 +178,8 @@ fn propagate_viability_update(
|
||||
|
||||
backend.write_block_entry(new_entry);
|
||||
|
||||
tree_frontier.extend(
|
||||
children.into_iter().map(|(h, update)| (BlockEntryRef::Hash(h), update))
|
||||
);
|
||||
tree_frontier
|
||||
.extend(children.into_iter().map(|(h, update)| (BlockEntryRef::Hash(h), update)));
|
||||
}
|
||||
|
||||
// Revisit the viability pivots now that we've traversed the entire subtree.
|
||||
@@ -229,12 +222,11 @@ fn propagate_viability_update(
|
||||
// Furthermore, if the set of viable leaves is empty, the
|
||||
// finalized block is implicitly the viable leaf.
|
||||
continue
|
||||
}
|
||||
Some(entry) => {
|
||||
},
|
||||
Some(entry) =>
|
||||
if entry.children.len() == pivot_count {
|
||||
viable_leaves.insert(entry.leaf_entry());
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,12 +246,7 @@ pub(crate) fn import_block(
|
||||
stagnant_at: Timestamp,
|
||||
) -> Result<(), Error> {
|
||||
add_block(backend, block_hash, block_number, parent_hash, weight, stagnant_at)?;
|
||||
apply_reversions(
|
||||
backend,
|
||||
block_hash,
|
||||
block_number,
|
||||
reversion_logs,
|
||||
)?;
|
||||
apply_reversions(backend, block_hash, block_number, reversion_logs)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -276,7 +263,9 @@ fn load_ancestor(
|
||||
block_number: BlockNumber,
|
||||
ancestor_number: BlockNumber,
|
||||
) -> Result<Option<BlockEntry>, Error> {
|
||||
if block_number <= ancestor_number { return Ok(None) }
|
||||
if block_number <= ancestor_number {
|
||||
return Ok(None)
|
||||
}
|
||||
|
||||
let mut current_hash = block_hash;
|
||||
let mut current_entry = None;
|
||||
@@ -289,7 +278,7 @@ fn load_ancestor(
|
||||
let parent_hash = entry.parent_hash;
|
||||
current_entry = Some(entry);
|
||||
current_hash = parent_hash;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,24 +303,22 @@ fn add_block(
|
||||
let mut leaves = backend.load_leaves()?;
|
||||
let parent_entry = backend.load_block_entry(&parent_hash)?;
|
||||
|
||||
let inherited_viability = parent_entry.as_ref()
|
||||
.and_then(|parent| parent.non_viable_ancestor_for_child());
|
||||
let inherited_viability =
|
||||
parent_entry.as_ref().and_then(|parent| parent.non_viable_ancestor_for_child());
|
||||
|
||||
// 1. Add the block to the DB assuming it's not reverted.
|
||||
backend.write_block_entry(
|
||||
BlockEntry {
|
||||
block_hash,
|
||||
block_number,
|
||||
parent_hash,
|
||||
children: Vec::new(),
|
||||
viability: ViabilityCriteria {
|
||||
earliest_unviable_ancestor: inherited_viability,
|
||||
explicitly_reverted: false,
|
||||
approval: Approval::Unapproved,
|
||||
},
|
||||
weight,
|
||||
}
|
||||
);
|
||||
backend.write_block_entry(BlockEntry {
|
||||
block_hash,
|
||||
block_number,
|
||||
parent_hash,
|
||||
children: Vec::new(),
|
||||
viability: ViabilityCriteria {
|
||||
earliest_unviable_ancestor: inherited_viability,
|
||||
explicitly_reverted: false,
|
||||
approval: Approval::Unapproved,
|
||||
},
|
||||
weight,
|
||||
});
|
||||
|
||||
// 2. Update leaves if inherited viability is fine.
|
||||
if inherited_viability.is_none() {
|
||||
@@ -370,38 +357,34 @@ fn apply_reversions(
|
||||
// Note: since revert numbers are in ascending order, the expensive propagation
|
||||
// of unviability is only heavy on the first log.
|
||||
for revert_number in reversions {
|
||||
let mut ancestor_entry = match load_ancestor(
|
||||
backend,
|
||||
block_hash,
|
||||
block_number,
|
||||
revert_number,
|
||||
)? {
|
||||
None => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
?block_hash,
|
||||
block_number,
|
||||
revert_target = revert_number,
|
||||
"The hammer has dropped. \
|
||||
let mut ancestor_entry =
|
||||
match load_ancestor(backend, block_hash, block_number, revert_number)? {
|
||||
None => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
?block_hash,
|
||||
block_number,
|
||||
revert_target = revert_number,
|
||||
"The hammer has dropped. \
|
||||
A block has indicated that its finalized ancestor be reverted. \
|
||||
Please inform an adult.",
|
||||
);
|
||||
);
|
||||
|
||||
continue
|
||||
}
|
||||
Some(ancestor_entry) => {
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
?block_hash,
|
||||
block_number,
|
||||
revert_target = revert_number,
|
||||
revert_hash = ?ancestor_entry.block_hash,
|
||||
"A block has signaled that its ancestor be reverted due to a bad parachain block.",
|
||||
);
|
||||
continue
|
||||
},
|
||||
Some(ancestor_entry) => {
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
?block_hash,
|
||||
block_number,
|
||||
revert_target = revert_number,
|
||||
revert_hash = ?ancestor_entry.block_hash,
|
||||
"A block has signaled that its ancestor be reverted due to a bad parachain block.",
|
||||
);
|
||||
|
||||
ancestor_entry
|
||||
}
|
||||
};
|
||||
ancestor_entry
|
||||
},
|
||||
};
|
||||
|
||||
ancestor_entry.viability.explicitly_reverted = true;
|
||||
propagate_viability_update(backend, ancestor_entry)?;
|
||||
@@ -431,8 +414,8 @@ pub(super) fn finalize_block<'a, B: Backend + 'a>(
|
||||
None => {
|
||||
// This implies that there are no unfinalized blocks and hence nothing
|
||||
// to update.
|
||||
return Ok(backend);
|
||||
}
|
||||
return Ok(backend)
|
||||
},
|
||||
Some(e) => e,
|
||||
};
|
||||
|
||||
@@ -474,9 +457,7 @@ pub(super) fn finalize_block<'a, B: Backend + 'a>(
|
||||
|
||||
// Add all children to the frontier.
|
||||
let next_height = dead_number + 1;
|
||||
frontier.extend(
|
||||
entry.into_iter().flat_map(|e| e.children).map(|h| (h, next_height))
|
||||
);
|
||||
frontier.extend(entry.into_iter().flat_map(|e| e.children).map(|h| (h, next_height)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,7 +514,6 @@ pub(super) fn approve_block(
|
||||
} else {
|
||||
backend.write_block_entry(entry);
|
||||
}
|
||||
|
||||
} else {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
|
||||
@@ -21,12 +21,12 @@
|
||||
//! [`Backend`], maintaining consistency between queries and temporary writes,
|
||||
//! before any commit to the underlying storage is made.
|
||||
|
||||
use polkadot_primitives::v1::{CandidateHash, SessionIndex};
|
||||
use polkadot_node_subsystem::SubsystemResult;
|
||||
use polkadot_primitives::v1::{CandidateHash, SessionIndex};
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::db::v1::{RecentDisputes, CandidateVotes};
|
||||
use super::db::v1::{CandidateVotes, RecentDisputes};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum BackendWriteOp {
|
||||
@@ -54,7 +54,8 @@ pub trait Backend {
|
||||
/// Atomically writes the list of operations, with later operations taking precedence over
|
||||
/// prior.
|
||||
fn write<I>(&mut self, ops: I) -> SubsystemResult<()>
|
||||
where I: IntoIterator<Item = BackendWriteOp>;
|
||||
where
|
||||
I: IntoIterator<Item = BackendWriteOp>;
|
||||
}
|
||||
|
||||
/// An in-memory overlay for the backend.
|
||||
@@ -112,7 +113,7 @@ impl<'a, B: 'a + Backend> OverlayedBackend<'a, B> {
|
||||
pub fn load_candidate_votes(
|
||||
&self,
|
||||
session: SessionIndex,
|
||||
candidate_hash: &CandidateHash
|
||||
candidate_hash: &CandidateHash,
|
||||
) -> SubsystemResult<Option<CandidateVotes>> {
|
||||
if let Some(val) = self.candidate_votes.get(&(session, *candidate_hash)) {
|
||||
return Ok(val.clone())
|
||||
@@ -142,7 +143,7 @@ impl<'a, B: 'a + Backend> OverlayedBackend<'a, B> {
|
||||
&mut self,
|
||||
session: SessionIndex,
|
||||
candidate_hash: CandidateHash,
|
||||
votes: CandidateVotes
|
||||
votes: CandidateVotes,
|
||||
) {
|
||||
self.candidate_votes.insert((session, candidate_hash), Some(votes));
|
||||
}
|
||||
@@ -150,34 +151,28 @@ impl<'a, B: 'a + Backend> OverlayedBackend<'a, B> {
|
||||
/// Prepare a deletion of the candidate votes under the indicated candidate.
|
||||
///
|
||||
/// Later calls to this function for the same candidate will override earlier ones.
|
||||
pub fn delete_candidate_votes(
|
||||
&mut self,
|
||||
session: SessionIndex,
|
||||
candidate_hash: CandidateHash,
|
||||
) {
|
||||
pub fn delete_candidate_votes(&mut self, session: SessionIndex, candidate_hash: CandidateHash) {
|
||||
self.candidate_votes.insert((session, candidate_hash), None);
|
||||
}
|
||||
|
||||
/// Transform this backend into a set of write-ops to be written to the inner backend.
|
||||
pub fn into_write_ops(self) -> impl Iterator<Item = BackendWriteOp> {
|
||||
let earliest_session_ops = self.earliest_session
|
||||
let earliest_session_ops = self
|
||||
.earliest_session
|
||||
.map(|s| BackendWriteOp::WriteEarliestSession(s))
|
||||
.into_iter();
|
||||
|
||||
let recent_dispute_ops = self.recent_disputes
|
||||
.map(|d| BackendWriteOp::WriteRecentDisputes(d))
|
||||
.into_iter();
|
||||
let recent_dispute_ops =
|
||||
self.recent_disputes.map(|d| BackendWriteOp::WriteRecentDisputes(d)).into_iter();
|
||||
|
||||
let candidate_vote_ops = self.candidate_votes
|
||||
.into_iter()
|
||||
.map(|((session, candidate), votes)| match votes {
|
||||
Some(votes) => BackendWriteOp::WriteCandidateVotes(session, candidate, votes),
|
||||
None => BackendWriteOp::DeleteCandidateVotes(session, candidate),
|
||||
});
|
||||
|
||||
earliest_session_ops
|
||||
.chain(recent_dispute_ops)
|
||||
.chain(candidate_vote_ops)
|
||||
let candidate_vote_ops =
|
||||
self.candidate_votes
|
||||
.into_iter()
|
||||
.map(|((session, candidate), votes)| match votes {
|
||||
Some(votes) => BackendWriteOp::WriteCandidateVotes(session, candidate, votes),
|
||||
None => BackendWriteOp::DeleteCandidateVotes(session, candidate),
|
||||
});
|
||||
|
||||
earliest_session_ops.chain(recent_dispute_ops).chain(candidate_vote_ops)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,19 +16,21 @@
|
||||
|
||||
//! `V1` database for the dispute coordinator.
|
||||
|
||||
use polkadot_node_subsystem::{SubsystemError, SubsystemResult};
|
||||
use polkadot_primitives::v1::{
|
||||
CandidateReceipt, ValidDisputeStatementKind, InvalidDisputeStatementKind, ValidatorIndex,
|
||||
ValidatorSignature, SessionIndex, CandidateHash, Hash,
|
||||
CandidateHash, CandidateReceipt, Hash, InvalidDisputeStatementKind, SessionIndex,
|
||||
ValidDisputeStatementKind, ValidatorIndex, ValidatorSignature,
|
||||
};
|
||||
use polkadot_node_subsystem::{SubsystemResult, SubsystemError};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use kvdb::{KeyValueDB, DBTransaction};
|
||||
use parity_scale_codec::{Encode, Decode};
|
||||
use kvdb::{DBTransaction, KeyValueDB};
|
||||
use parity_scale_codec::{Decode, Encode};
|
||||
|
||||
use crate::{DISPUTE_WINDOW, DisputeStatus};
|
||||
use crate::backend::{Backend, BackendWriteOp, OverlayedBackend};
|
||||
use crate::{
|
||||
backend::{Backend, BackendWriteOp, OverlayedBackend},
|
||||
DisputeStatus, DISPUTE_WINDOW,
|
||||
};
|
||||
|
||||
const RECENT_DISPUTES_KEY: &[u8; 15] = b"recent-disputes";
|
||||
const EARLIEST_SESSION_KEY: &[u8; 16] = b"earliest-session";
|
||||
@@ -41,10 +43,7 @@ pub struct DbBackend {
|
||||
|
||||
impl DbBackend {
|
||||
pub fn new(db: Arc<dyn KeyValueDB>, config: ColumnConfiguration) -> Self {
|
||||
Self {
|
||||
inner: db,
|
||||
config,
|
||||
}
|
||||
Self { inner: db, config }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,38 +70,28 @@ impl Backend for DbBackend {
|
||||
/// Atomically writes the list of operations, with later operations taking precedence over
|
||||
/// prior.
|
||||
fn write<I>(&mut self, ops: I) -> SubsystemResult<()>
|
||||
where I: IntoIterator<Item = BackendWriteOp>
|
||||
where
|
||||
I: IntoIterator<Item = BackendWriteOp>,
|
||||
{
|
||||
let mut tx = DBTransaction::new();
|
||||
for op in ops {
|
||||
match op {
|
||||
BackendWriteOp::WriteEarliestSession(session) => {
|
||||
tx.put_vec(
|
||||
self.config.col_data,
|
||||
EARLIEST_SESSION_KEY,
|
||||
session.encode(),
|
||||
);
|
||||
}
|
||||
tx.put_vec(self.config.col_data, EARLIEST_SESSION_KEY, session.encode());
|
||||
},
|
||||
BackendWriteOp::WriteRecentDisputes(recent_disputes) => {
|
||||
tx.put_vec(
|
||||
self.config.col_data,
|
||||
RECENT_DISPUTES_KEY,
|
||||
recent_disputes.encode(),
|
||||
);
|
||||
}
|
||||
tx.put_vec(self.config.col_data, RECENT_DISPUTES_KEY, recent_disputes.encode());
|
||||
},
|
||||
BackendWriteOp::WriteCandidateVotes(session, candidate_hash, votes) => {
|
||||
tx.put_vec(
|
||||
self.config.col_data,
|
||||
&candidate_votes_key(session, &candidate_hash),
|
||||
votes.encode(),
|
||||
);
|
||||
}
|
||||
},
|
||||
BackendWriteOp::DeleteCandidateVotes(session, candidate_hash) => {
|
||||
tx.delete(
|
||||
self.config.col_data,
|
||||
&candidate_votes_key(session, &candidate_hash),
|
||||
);
|
||||
}
|
||||
tx.delete(self.config.col_data, &candidate_votes_key(session, &candidate_hash));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,14 +163,10 @@ pub enum Error {
|
||||
/// Result alias for DB errors.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
fn load_decode<D: Decode>(db: &dyn KeyValueDB, col_data: u32, key: &[u8])
|
||||
-> Result<Option<D>>
|
||||
{
|
||||
fn load_decode<D: Decode>(db: &dyn KeyValueDB, col_data: u32, key: &[u8]) -> Result<Option<D>> {
|
||||
match db.get(col_data, key)? {
|
||||
None => Ok(None),
|
||||
Some(raw) => D::decode(&mut &raw[..])
|
||||
.map(Some)
|
||||
.map_err(Into::into),
|
||||
Some(raw) => D::decode(&mut &raw[..]).map(Some).map_err(Into::into),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +216,7 @@ pub(crate) fn note_current_session(
|
||||
None => {
|
||||
// First launch - write new-earliest.
|
||||
overlay_db.write_earliest_session(new_earliest);
|
||||
}
|
||||
},
|
||||
Some(prev_earliest) if new_earliest > prev_earliest => {
|
||||
// Prune all data in the outdated sessions.
|
||||
overlay_db.write_earliest_session(new_earliest);
|
||||
@@ -240,10 +225,7 @@ pub(crate) fn note_current_session(
|
||||
{
|
||||
let mut recent_disputes = overlay_db.load_recent_disputes()?.unwrap_or_default();
|
||||
|
||||
let lower_bound = (
|
||||
new_earliest,
|
||||
CandidateHash(Hash::repeat_byte(0x00)),
|
||||
);
|
||||
let lower_bound = (new_earliest, CandidateHash(Hash::repeat_byte(0x00)));
|
||||
|
||||
let new_recent_disputes = recent_disputes.split_off(&lower_bound);
|
||||
// Any remanining disputes are considered ancient and must be pruned.
|
||||
@@ -256,10 +238,10 @@ pub(crate) fn note_current_session(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Some(_) => {
|
||||
// nothing to do.
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -285,13 +267,17 @@ mod tests {
|
||||
overlay_db.write_earliest_session(0);
|
||||
overlay_db.write_earliest_session(1);
|
||||
|
||||
overlay_db.write_recent_disputes(vec![
|
||||
((0, CandidateHash(Hash::repeat_byte(0))), DisputeStatus::Active),
|
||||
].into_iter().collect());
|
||||
overlay_db.write_recent_disputes(
|
||||
vec![((0, CandidateHash(Hash::repeat_byte(0))), DisputeStatus::Active)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
);
|
||||
|
||||
overlay_db.write_recent_disputes(vec![
|
||||
((1, CandidateHash(Hash::repeat_byte(1))), DisputeStatus::Active),
|
||||
].into_iter().collect());
|
||||
overlay_db.write_recent_disputes(
|
||||
vec![((1, CandidateHash(Hash::repeat_byte(1))), DisputeStatus::Active)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
);
|
||||
|
||||
overlay_db.write_candidate_votes(
|
||||
1,
|
||||
@@ -318,23 +304,23 @@ mod tests {
|
||||
);
|
||||
|
||||
// Test that overlay returns the correct values before committing.
|
||||
assert_eq!(
|
||||
overlay_db.load_earliest_session().unwrap().unwrap(),
|
||||
1,
|
||||
);
|
||||
assert_eq!(overlay_db.load_earliest_session().unwrap().unwrap(), 1,);
|
||||
|
||||
assert_eq!(
|
||||
overlay_db.load_recent_disputes().unwrap().unwrap(),
|
||||
vec![
|
||||
((1, CandidateHash(Hash::repeat_byte(1))), DisputeStatus::Active),
|
||||
].into_iter().collect()
|
||||
vec![((1, CandidateHash(Hash::repeat_byte(1))), DisputeStatus::Active),]
|
||||
.into_iter()
|
||||
.collect()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
overlay_db.load_candidate_votes(
|
||||
1,
|
||||
&CandidateHash(Hash::repeat_byte(1))
|
||||
).unwrap().unwrap().candidate_receipt.descriptor.para_id,
|
||||
overlay_db
|
||||
.load_candidate_votes(1, &CandidateHash(Hash::repeat_byte(1)))
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.candidate_receipt
|
||||
.descriptor
|
||||
.para_id,
|
||||
ParaId::from(5),
|
||||
);
|
||||
|
||||
@@ -342,23 +328,23 @@ mod tests {
|
||||
backend.write(write_ops).unwrap();
|
||||
|
||||
// Test that subsequent writes were written.
|
||||
assert_eq!(
|
||||
backend.load_earliest_session().unwrap().unwrap(),
|
||||
1,
|
||||
);
|
||||
assert_eq!(backend.load_earliest_session().unwrap().unwrap(), 1,);
|
||||
|
||||
assert_eq!(
|
||||
backend.load_recent_disputes().unwrap().unwrap(),
|
||||
vec![
|
||||
((1, CandidateHash(Hash::repeat_byte(1))), DisputeStatus::Active),
|
||||
].into_iter().collect()
|
||||
vec![((1, CandidateHash(Hash::repeat_byte(1))), DisputeStatus::Active),]
|
||||
.into_iter()
|
||||
.collect()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
backend.load_candidate_votes(
|
||||
1,
|
||||
&CandidateHash(Hash::repeat_byte(1))
|
||||
).unwrap().unwrap().candidate_receipt.descriptor.para_id,
|
||||
backend
|
||||
.load_candidate_votes(1, &CandidateHash(Hash::repeat_byte(1)))
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.candidate_receipt
|
||||
.descriptor
|
||||
.para_id,
|
||||
ParaId::from(5),
|
||||
);
|
||||
}
|
||||
@@ -377,17 +363,20 @@ mod tests {
|
||||
candidate_receipt: Default::default(),
|
||||
valid: Vec::new(),
|
||||
invalid: Vec::new(),
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let write_ops = overlay_db.into_write_ops();
|
||||
backend.write(write_ops).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
backend.load_candidate_votes(
|
||||
1,
|
||||
&CandidateHash(Hash::repeat_byte(1))
|
||||
).unwrap().unwrap().candidate_receipt.descriptor.para_id,
|
||||
backend
|
||||
.load_candidate_votes(1, &CandidateHash(Hash::repeat_byte(1)))
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.candidate_receipt
|
||||
.descriptor
|
||||
.para_id,
|
||||
ParaId::from(0),
|
||||
);
|
||||
|
||||
@@ -404,7 +393,7 @@ mod tests {
|
||||
},
|
||||
valid: Vec::new(),
|
||||
invalid: Vec::new(),
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
overlay_db.delete_candidate_votes(1, CandidateHash(Hash::repeat_byte(1)));
|
||||
@@ -412,12 +401,10 @@ mod tests {
|
||||
let write_ops = overlay_db.into_write_ops();
|
||||
backend.write(write_ops).unwrap();
|
||||
|
||||
assert!(
|
||||
backend.load_candidate_votes(
|
||||
1,
|
||||
&CandidateHash(Hash::repeat_byte(1))
|
||||
).unwrap().is_none()
|
||||
);
|
||||
assert!(backend
|
||||
.load_candidate_votes(1, &CandidateHash(Hash::repeat_byte(1)))
|
||||
.unwrap()
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -445,36 +432,24 @@ mod tests {
|
||||
|
||||
let mut overlay_db = OverlayedBackend::new(&backend);
|
||||
overlay_db.write_earliest_session(prev_earliest_session);
|
||||
overlay_db.write_recent_disputes(vec![
|
||||
((very_old, hash_a), DisputeStatus::Active),
|
||||
((slightly_old, hash_b), DisputeStatus::Active),
|
||||
((new_earliest_session, hash_c), DisputeStatus::Active),
|
||||
((very_recent, hash_d), DisputeStatus::Active),
|
||||
].into_iter().collect());
|
||||
|
||||
overlay_db.write_candidate_votes(
|
||||
very_old,
|
||||
hash_a,
|
||||
blank_candidate_votes(),
|
||||
overlay_db.write_recent_disputes(
|
||||
vec![
|
||||
((very_old, hash_a), DisputeStatus::Active),
|
||||
((slightly_old, hash_b), DisputeStatus::Active),
|
||||
((new_earliest_session, hash_c), DisputeStatus::Active),
|
||||
((very_recent, hash_d), DisputeStatus::Active),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
);
|
||||
|
||||
overlay_db.write_candidate_votes(
|
||||
slightly_old,
|
||||
hash_b,
|
||||
blank_candidate_votes(),
|
||||
);
|
||||
overlay_db.write_candidate_votes(very_old, hash_a, blank_candidate_votes());
|
||||
|
||||
overlay_db.write_candidate_votes(
|
||||
new_earliest_session,
|
||||
hash_c,
|
||||
blank_candidate_votes(),
|
||||
);
|
||||
overlay_db.write_candidate_votes(slightly_old, hash_b, blank_candidate_votes());
|
||||
|
||||
overlay_db.write_candidate_votes(
|
||||
very_recent,
|
||||
hash_d,
|
||||
blank_candidate_votes(),
|
||||
);
|
||||
overlay_db.write_candidate_votes(new_earliest_session, hash_c, blank_candidate_votes());
|
||||
|
||||
overlay_db.write_candidate_votes(very_recent, hash_d, blank_candidate_votes());
|
||||
|
||||
let write_ops = overlay_db.into_write_ops();
|
||||
backend.write(write_ops).unwrap();
|
||||
@@ -482,22 +457,24 @@ mod tests {
|
||||
let mut overlay_db = OverlayedBackend::new(&backend);
|
||||
note_current_session(&mut overlay_db, current_session).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
overlay_db.load_earliest_session().unwrap(),
|
||||
Some(new_earliest_session),
|
||||
);
|
||||
assert_eq!(overlay_db.load_earliest_session().unwrap(), Some(new_earliest_session),);
|
||||
|
||||
assert_eq!(
|
||||
overlay_db.load_recent_disputes().unwrap().unwrap(),
|
||||
vec![
|
||||
((new_earliest_session, hash_c), DisputeStatus::Active),
|
||||
((very_recent, hash_d), DisputeStatus::Active),
|
||||
].into_iter().collect(),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
);
|
||||
|
||||
assert!(overlay_db.load_candidate_votes(very_old, &hash_a).unwrap().is_none());
|
||||
assert!(overlay_db.load_candidate_votes(slightly_old, &hash_b).unwrap().is_none());
|
||||
assert!(overlay_db.load_candidate_votes(new_earliest_session, &hash_c).unwrap().is_some());
|
||||
assert!(overlay_db
|
||||
.load_candidate_votes(new_earliest_session, &hash_c)
|
||||
.unwrap()
|
||||
.is_some());
|
||||
assert!(overlay_db.load_candidate_votes(very_recent, &hash_d).unwrap().is_some());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,38 +25,42 @@
|
||||
//! another node, this will trigger the dispute participation subsystem to recover and validate the block and call
|
||||
//! back to this subsystem.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
sync::Arc,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use polkadot_node_primitives::{CandidateVotes, DISPUTE_WINDOW, DisputeMessage, SignedDisputeStatement, DisputeMessageCheckError};
|
||||
use polkadot_node_primitives::{
|
||||
CandidateVotes, DisputeMessage, DisputeMessageCheckError, SignedDisputeStatement,
|
||||
DISPUTE_WINDOW,
|
||||
};
|
||||
use polkadot_node_subsystem::{
|
||||
overseer, SubsystemContext, FromOverseer, OverseerSignal, SpawnedSubsystem, SubsystemError,
|
||||
errors::{ChainApiError, RuntimeApiError},
|
||||
messages::{
|
||||
ChainApiMessage, DisputeCoordinatorMessage, DisputeDistributionMessage,
|
||||
DisputeParticipationMessage, ImportStatementsResult, BlockDescription,
|
||||
}
|
||||
BlockDescription, ChainApiMessage, DisputeCoordinatorMessage, DisputeDistributionMessage,
|
||||
DisputeParticipationMessage, ImportStatementsResult,
|
||||
},
|
||||
overseer, FromOverseer, OverseerSignal, SpawnedSubsystem, SubsystemContext, SubsystemError,
|
||||
};
|
||||
use polkadot_node_subsystem_util::rolling_session_window::{
|
||||
RollingSessionWindow, SessionWindowUpdate,
|
||||
};
|
||||
use polkadot_primitives::v1::{
|
||||
BlockNumber, CandidateHash, CandidateReceipt, DisputeStatement, Hash,
|
||||
SessionIndex, SessionInfo, ValidatorIndex, ValidatorPair, ValidatorSignature
|
||||
BlockNumber, CandidateHash, CandidateReceipt, DisputeStatement, Hash, SessionIndex,
|
||||
SessionInfo, ValidatorIndex, ValidatorPair, ValidatorSignature,
|
||||
};
|
||||
|
||||
use futures::prelude::*;
|
||||
use futures::channel::oneshot;
|
||||
use futures::{channel::oneshot, prelude::*};
|
||||
use kvdb::KeyValueDB;
|
||||
use parity_scale_codec::{Encode, Decode, Error as CodecError};
|
||||
use parity_scale_codec::{Decode, Encode, Error as CodecError};
|
||||
use sc_keystore::LocalKeystore;
|
||||
|
||||
use db::v1::{RecentDisputes, DbBackend};
|
||||
use backend::{Backend, OverlayedBackend};
|
||||
use db::v1::{DbBackend, RecentDisputes};
|
||||
|
||||
mod db;
|
||||
mod backend;
|
||||
mod db;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -116,11 +120,7 @@ pub struct DisputeCoordinatorSubsystem {
|
||||
|
||||
impl DisputeCoordinatorSubsystem {
|
||||
/// Create a new instance of the subsystem.
|
||||
pub fn new(
|
||||
store: Arc<dyn KeyValueDB>,
|
||||
config: Config,
|
||||
keystore: Arc<LocalKeystore>,
|
||||
) -> Self {
|
||||
pub fn new(store: Arc<dyn KeyValueDB>, config: Config, keystore: Arc<LocalKeystore>) -> Self {
|
||||
DisputeCoordinatorSubsystem { store, config, keystore }
|
||||
}
|
||||
}
|
||||
@@ -132,14 +132,9 @@ where
|
||||
{
|
||||
fn start(self, ctx: Context) -> SpawnedSubsystem {
|
||||
let backend = DbBackend::new(self.store.clone(), self.config.column_config());
|
||||
let future = run(self, ctx, backend, Box::new(SystemClock))
|
||||
.map(|_| Ok(()))
|
||||
.boxed();
|
||||
let future = run(self, ctx, backend, Box::new(SystemClock)).map(|_| Ok(())).boxed();
|
||||
|
||||
SpawnedSubsystem {
|
||||
name: "dispute-coordinator-subsystem",
|
||||
future,
|
||||
}
|
||||
SpawnedSubsystem { name: "dispute-coordinator-subsystem", future }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +162,7 @@ impl Clock for SystemClock {
|
||||
);
|
||||
|
||||
0
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -210,8 +205,8 @@ impl Error {
|
||||
fn trace(&self) {
|
||||
match self {
|
||||
// don't spam the log with spurious errors
|
||||
Self::RuntimeApi(_) |
|
||||
Self::Oneshot(_) => tracing::debug!(target: LOG_TARGET, err = ?self),
|
||||
Self::RuntimeApi(_) | Self::Oneshot(_) =>
|
||||
tracing::debug!(target: LOG_TARGET, err = ?self),
|
||||
// it's worth reporting otherwise
|
||||
_ => tracing::warn!(target: LOG_TARGET, err = ?self),
|
||||
}
|
||||
@@ -260,8 +255,10 @@ impl DisputeStatus {
|
||||
pub fn concluded_against(self, now: Timestamp) -> DisputeStatus {
|
||||
match self {
|
||||
DisputeStatus::Active => DisputeStatus::ConcludedAgainst(now),
|
||||
DisputeStatus::ConcludedFor(at) => DisputeStatus::ConcludedAgainst(std::cmp::min(at, now)),
|
||||
DisputeStatus::ConcludedAgainst(at) => DisputeStatus::ConcludedAgainst(std::cmp::min(at, now)),
|
||||
DisputeStatus::ConcludedFor(at) =>
|
||||
DisputeStatus::ConcludedAgainst(std::cmp::min(at, now)),
|
||||
DisputeStatus::ConcludedAgainst(at) =>
|
||||
DisputeStatus::ConcludedAgainst(std::cmp::min(at, now)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,8 +284,7 @@ async fn run<B, Context>(
|
||||
mut ctx: Context,
|
||||
mut backend: B,
|
||||
clock: Box<dyn Clock>,
|
||||
)
|
||||
where
|
||||
) where
|
||||
Context: overseer::SubsystemContext<Message = DisputeCoordinatorMessage>,
|
||||
Context: SubsystemContext<Message = DisputeCoordinatorMessage>,
|
||||
B: Backend,
|
||||
@@ -300,13 +296,13 @@ where
|
||||
e.trace();
|
||||
|
||||
if let Error::Subsystem(SubsystemError::Context(_)) = e {
|
||||
break;
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
Ok(()) => {
|
||||
tracing::info!(target: LOG_TARGET, "received `Conclude` signal, exiting");
|
||||
break;
|
||||
}
|
||||
break
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -337,34 +333,22 @@ where
|
||||
loop {
|
||||
let mut overlay_db = OverlayedBackend::new(backend);
|
||||
match ctx.recv().await? {
|
||||
FromOverseer::Signal(OverseerSignal::Conclude) => {
|
||||
return Ok(())
|
||||
}
|
||||
FromOverseer::Signal(OverseerSignal::Conclude) => return Ok(()),
|
||||
FromOverseer::Signal(OverseerSignal::ActiveLeaves(update)) => {
|
||||
handle_new_activations(
|
||||
ctx,
|
||||
&mut overlay_db,
|
||||
&mut state,
|
||||
update.activated.into_iter().map(|a| a.hash),
|
||||
).await?;
|
||||
)
|
||||
.await?;
|
||||
if !state.recovery_state.complete() {
|
||||
handle_startup(
|
||||
ctx,
|
||||
&mut overlay_db,
|
||||
&mut state,
|
||||
).await?;
|
||||
handle_startup(ctx, &mut overlay_db, &mut state).await?;
|
||||
}
|
||||
}
|
||||
},
|
||||
FromOverseer::Signal(OverseerSignal::BlockFinalized(_, _)) => {},
|
||||
FromOverseer::Communication { msg } => {
|
||||
handle_incoming(
|
||||
ctx,
|
||||
&mut overlay_db,
|
||||
&mut state,
|
||||
msg,
|
||||
clock.now(),
|
||||
).await?
|
||||
}
|
||||
FromOverseer::Communication { msg } =>
|
||||
handle_incoming(ctx, &mut overlay_db, &mut state, msg, clock.now()).await?,
|
||||
}
|
||||
|
||||
if !overlay_db.is_empty() {
|
||||
@@ -395,12 +379,13 @@ where
|
||||
Ok(None) => return Ok(()),
|
||||
Err(e) => {
|
||||
tracing::error!(target: LOG_TARGET, "Failed initial load of recent disputes: {:?}", e);
|
||||
return Err(e.into());
|
||||
return Err(e.into())
|
||||
},
|
||||
};
|
||||
|
||||
// Filter out disputes that have already concluded.
|
||||
let active_disputes = recent_disputes.into_iter()
|
||||
let active_disputes = recent_disputes
|
||||
.into_iter()
|
||||
.filter(|(_, status)| *status == DisputeStatus::Active)
|
||||
.collect::<RecentDisputes>();
|
||||
|
||||
@@ -409,7 +394,11 @@ where
|
||||
Ok(Some(votes)) => votes.into(),
|
||||
Ok(None) => continue,
|
||||
Err(e) => {
|
||||
tracing::error!(target: LOG_TARGET, "Failed initial load of candidate votes: {:?}", e);
|
||||
tracing::error!(
|
||||
target: LOG_TARGET,
|
||||
"Failed initial load of candidate votes: {:?}",
|
||||
e
|
||||
);
|
||||
continue
|
||||
},
|
||||
};
|
||||
@@ -422,7 +411,7 @@ where
|
||||
"Missing info for session which has an active dispute",
|
||||
);
|
||||
continue
|
||||
}
|
||||
},
|
||||
Some(info) => info.validators.clone(),
|
||||
};
|
||||
|
||||
@@ -434,16 +423,18 @@ where
|
||||
// 1) their statement already exists, or
|
||||
// 2) the validator key is not in the local keystore (i.e. the validator is remote).
|
||||
// The remaining set only contains local validators that are also missing statements.
|
||||
let missing_local_statement = validators.iter()
|
||||
let missing_local_statement = validators
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, validator)| (ValidatorIndex(index as _), validator))
|
||||
.any(|(index, validator)|
|
||||
.any(|(index, validator)| {
|
||||
!voted_indices.contains(&index) &&
|
||||
state.keystore
|
||||
.key_pair::<ValidatorPair>(validator)
|
||||
.ok()
|
||||
.map_or(false, |v| v.is_some())
|
||||
);
|
||||
state
|
||||
.keystore
|
||||
.key_pair::<ValidatorPair>(validator)
|
||||
.ok()
|
||||
.map_or(false, |v| v.is_some())
|
||||
});
|
||||
|
||||
// Send a `DisputeParticipationMessage` for all non-concluded disputes which do not have a
|
||||
// recorded local statement.
|
||||
@@ -455,10 +446,14 @@ where
|
||||
session,
|
||||
n_validators: n_validators as u32,
|
||||
report_availability,
|
||||
}).await;
|
||||
})
|
||||
.await;
|
||||
|
||||
if !receive_availability.await? {
|
||||
tracing::debug!(target: LOG_TARGET, "Participation failed. Candidate not available");
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Participation failed. Candidate not available"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -467,7 +462,8 @@ where
|
||||
}
|
||||
|
||||
async fn handle_new_activations(
|
||||
ctx: &mut (impl SubsystemContext<Message = DisputeCoordinatorMessage> + overseer::SubsystemContext<Message = DisputeCoordinatorMessage>),
|
||||
ctx: &mut (impl SubsystemContext<Message = DisputeCoordinatorMessage>
|
||||
+ overseer::SubsystemContext<Message = DisputeCoordinatorMessage>),
|
||||
overlay_db: &mut OverlayedBackend<'_, impl Backend>,
|
||||
state: &mut State,
|
||||
new_activations: impl IntoIterator<Item = Hash>,
|
||||
@@ -476,9 +472,7 @@ async fn handle_new_activations(
|
||||
let block_header = {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
ctx.send_message(
|
||||
ChainApiMessage::BlockHeader(new_leaf, tx)
|
||||
).await;
|
||||
ctx.send_message(ChainApiMessage::BlockHeader(new_leaf, tx)).await;
|
||||
|
||||
match rx.await?? {
|
||||
None => continue,
|
||||
@@ -486,11 +480,11 @@ async fn handle_new_activations(
|
||||
}
|
||||
};
|
||||
|
||||
match state.rolling_session_window.cache_session_info_for_head(
|
||||
ctx,
|
||||
new_leaf,
|
||||
&block_header,
|
||||
).await {
|
||||
match state
|
||||
.rolling_session_window
|
||||
.cache_session_info_for_head(ctx, new_leaf, &block_header)
|
||||
.await
|
||||
{
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
@@ -499,24 +493,19 @@ async fn handle_new_activations(
|
||||
);
|
||||
|
||||
continue
|
||||
}
|
||||
Ok(SessionWindowUpdate::Initialized { window_end, .. })
|
||||
| Ok(SessionWindowUpdate::Advanced { new_window_end: window_end, .. })
|
||||
=> {
|
||||
},
|
||||
Ok(SessionWindowUpdate::Initialized { window_end, .. }) |
|
||||
Ok(SessionWindowUpdate::Advanced { new_window_end: window_end, .. }) => {
|
||||
let session = window_end;
|
||||
if state.highest_session.map_or(true, |s| s < session) {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
session,
|
||||
"Observed new session. Pruning",
|
||||
);
|
||||
tracing::trace!(target: LOG_TARGET, session, "Observed new session. Pruning",);
|
||||
|
||||
state.highest_session = Some(session);
|
||||
|
||||
db::v1::note_current_session(overlay_db, session)?;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -548,26 +537,21 @@ async fn handle_incoming(
|
||||
statements,
|
||||
now,
|
||||
pending_confirmation,
|
||||
).await?;
|
||||
}
|
||||
)
|
||||
.await?;
|
||||
},
|
||||
DisputeCoordinatorMessage::RecentDisputes(rx) => {
|
||||
let recent_disputes = overlay_db.load_recent_disputes()?.unwrap_or_default();
|
||||
let _ = rx.send(recent_disputes.keys().cloned().collect());
|
||||
}
|
||||
},
|
||||
DisputeCoordinatorMessage::ActiveDisputes(rx) => {
|
||||
let recent_disputes = overlay_db.load_recent_disputes()?.unwrap_or_default();
|
||||
let _ = rx.send(collect_active(recent_disputes, now));
|
||||
}
|
||||
DisputeCoordinatorMessage::QueryCandidateVotes(
|
||||
query,
|
||||
rx
|
||||
) => {
|
||||
},
|
||||
DisputeCoordinatorMessage::QueryCandidateVotes(query, rx) => {
|
||||
let mut query_output = Vec::new();
|
||||
for (session_index, candidate_hash) in query.into_iter() {
|
||||
if let Some(v) = overlay_db.load_candidate_votes(
|
||||
session_index,
|
||||
&candidate_hash,
|
||||
)? {
|
||||
if let Some(v) = overlay_db.load_candidate_votes(session_index, &candidate_hash)? {
|
||||
query_output.push((session_index, candidate_hash, v.into()));
|
||||
} else {
|
||||
tracing::debug!(
|
||||
@@ -578,7 +562,7 @@ async fn handle_incoming(
|
||||
}
|
||||
}
|
||||
let _ = rx.send(query_output);
|
||||
}
|
||||
},
|
||||
DisputeCoordinatorMessage::IssueLocalStatement(
|
||||
session,
|
||||
candidate_hash,
|
||||
@@ -594,33 +578,37 @@ async fn handle_incoming(
|
||||
session,
|
||||
valid,
|
||||
now,
|
||||
).await?;
|
||||
}
|
||||
)
|
||||
.await?;
|
||||
},
|
||||
DisputeCoordinatorMessage::DetermineUndisputedChain {
|
||||
base_number,
|
||||
block_descriptions,
|
||||
tx,
|
||||
} => {
|
||||
let undisputed_chain = determine_undisputed_chain(
|
||||
overlay_db,
|
||||
base_number,
|
||||
block_descriptions
|
||||
)?;
|
||||
let undisputed_chain =
|
||||
determine_undisputed_chain(overlay_db, base_number, block_descriptions)?;
|
||||
|
||||
let _ = tx.send(undisputed_chain);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn collect_active(recent_disputes: RecentDisputes, now: Timestamp) -> Vec<(SessionIndex, CandidateHash)> {
|
||||
recent_disputes.iter().filter_map(|(disputed, status)|
|
||||
status.concluded_at().filter(|at| at + ACTIVE_DURATION_SECS < now).map_or(
|
||||
Some(*disputed),
|
||||
|_| None,
|
||||
)
|
||||
).collect()
|
||||
fn collect_active(
|
||||
recent_disputes: RecentDisputes,
|
||||
now: Timestamp,
|
||||
) -> Vec<(SessionIndex, CandidateHash)> {
|
||||
recent_disputes
|
||||
.iter()
|
||||
.filter_map(|(disputed, status)| {
|
||||
status
|
||||
.concluded_at()
|
||||
.filter(|at| at + ACTIVE_DURATION_SECS < now)
|
||||
.map_or(Some(*disputed), |_| None)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn insert_into_statement_vec<T>(
|
||||
@@ -649,11 +637,12 @@ async fn handle_import_statements(
|
||||
pending_confirmation: oneshot::Sender<ImportStatementsResult>,
|
||||
) -> Result<(), Error> {
|
||||
if state.highest_session.map_or(true, |h| session + DISPUTE_WINDOW < h) {
|
||||
|
||||
// It is not valid to participate in an ancient dispute (spam?).
|
||||
pending_confirmation.send(ImportStatementsResult::InvalidImport).map_err(|_| Error::OneshotSend)?;
|
||||
pending_confirmation
|
||||
.send(ImportStatementsResult::InvalidImport)
|
||||
.map_err(|_| Error::OneshotSend)?;
|
||||
|
||||
return Ok(());
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let validators = match state.rolling_session_window.session_info(session) {
|
||||
@@ -669,7 +658,7 @@ async fn handle_import_statements(
|
||||
.map_err(|_| Error::OneshotSend)?;
|
||||
|
||||
return Ok(())
|
||||
}
|
||||
},
|
||||
Some(info) => info.validators.clone(),
|
||||
};
|
||||
|
||||
@@ -677,7 +666,8 @@ async fn handle_import_statements(
|
||||
|
||||
let supermajority_threshold = polkadot_primitives::v1::supermajority_threshold(n_validators);
|
||||
|
||||
let mut votes = overlay_db.load_candidate_votes(session, &candidate_hash)?
|
||||
let mut votes = overlay_db
|
||||
.load_candidate_votes(session, &candidate_hash)?
|
||||
.map(CandidateVotes::from)
|
||||
.unwrap_or_else(|| CandidateVotes {
|
||||
candidate_receipt: candidate_receipt.clone(),
|
||||
@@ -687,7 +677,8 @@ async fn handle_import_statements(
|
||||
|
||||
// Update candidate votes.
|
||||
for (statement, val_index) in statements {
|
||||
if validators.get(val_index.0 as usize)
|
||||
if validators
|
||||
.get(val_index.0 as usize)
|
||||
.map_or(true, |v| v != statement.validator_public())
|
||||
{
|
||||
tracing::debug!(
|
||||
@@ -709,7 +700,7 @@ async fn handle_import_statements(
|
||||
val_index,
|
||||
statement.validator_signature().clone(),
|
||||
);
|
||||
}
|
||||
},
|
||||
DisputeStatement::Invalid(invalid_kind) => {
|
||||
insert_into_statement_vec(
|
||||
&mut votes.invalid,
|
||||
@@ -717,7 +708,7 @@ async fn handle_import_statements(
|
||||
val_index,
|
||||
statement.validator_signature().clone(),
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -766,7 +757,8 @@ async fn handle_import_statements(
|
||||
session,
|
||||
n_validators: n_validators as u32,
|
||||
report_availability,
|
||||
}).await;
|
||||
})
|
||||
.await;
|
||||
|
||||
if !receive_availability.await.map_err(Error::Oneshot)? {
|
||||
// If the data is not available, we disregard the dispute votes.
|
||||
@@ -775,7 +767,8 @@ async fn handle_import_statements(
|
||||
//
|
||||
// We expect that if the candidate is truly disputed that the higher-level network
|
||||
// code will retry.
|
||||
pending_confirmation.send(ImportStatementsResult::InvalidImport)
|
||||
pending_confirmation
|
||||
.send(ImportStatementsResult::InvalidImport)
|
||||
.map_err(|_| Error::OneshotSend)?;
|
||||
|
||||
tracing::debug!(
|
||||
@@ -819,13 +812,14 @@ async fn issue_local_statement(
|
||||
);
|
||||
|
||||
return Ok(())
|
||||
}
|
||||
},
|
||||
Some(info) => info,
|
||||
};
|
||||
|
||||
let validators = info.validators.clone();
|
||||
|
||||
let votes = overlay_db.load_candidate_votes(session, &candidate_hash)?
|
||||
let votes = overlay_db
|
||||
.load_candidate_votes(session, &candidate_hash)?
|
||||
.map(CandidateVotes::from)
|
||||
.unwrap_or_else(|| CandidateVotes {
|
||||
candidate_receipt: candidate_receipt.clone(),
|
||||
@@ -841,7 +835,9 @@ async fn issue_local_statement(
|
||||
let voted_indices: HashSet<_> = voted_indices.into_iter().collect();
|
||||
for (index, validator) in validators.iter().enumerate() {
|
||||
let index = ValidatorIndex(index as _);
|
||||
if voted_indices.contains(&index) { continue }
|
||||
if voted_indices.contains(&index) {
|
||||
continue
|
||||
}
|
||||
if state.keystore.key_pair::<ValidatorPair>(validator).ok().flatten().is_none() {
|
||||
continue
|
||||
}
|
||||
@@ -853,20 +849,21 @@ async fn issue_local_statement(
|
||||
candidate_hash,
|
||||
session,
|
||||
validator.clone(),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Ok(Some(signed_dispute_statement)) => {
|
||||
statements.push((signed_dispute_statement, index));
|
||||
}
|
||||
Ok(None) => {}
|
||||
},
|
||||
Ok(None) => {},
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
target: LOG_TARGET,
|
||||
err = ?e,
|
||||
"Encountered keystore error while signing dispute statement",
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -874,20 +871,15 @@ async fn issue_local_statement(
|
||||
for (statement, index) in &statements {
|
||||
let dispute_message = match make_dispute_message(info, &votes, statement.clone(), *index) {
|
||||
Err(err) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
?err,
|
||||
"Creating dispute message failed."
|
||||
);
|
||||
tracing::debug!(target: LOG_TARGET, ?err, "Creating dispute message failed.");
|
||||
continue
|
||||
}
|
||||
},
|
||||
Ok(dispute_message) => dispute_message,
|
||||
};
|
||||
|
||||
ctx.send_message(DisputeDistributionMessage::SendDispute(dispute_message)).await;
|
||||
}
|
||||
|
||||
|
||||
// Do import
|
||||
if !statements.is_empty() {
|
||||
let (pending_confirmation, _rx) = oneshot::channel();
|
||||
@@ -901,7 +893,8 @@ async fn issue_local_statement(
|
||||
statements,
|
||||
now,
|
||||
pending_confirmation,
|
||||
).await?;
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -923,42 +916,52 @@ fn make_dispute_message(
|
||||
info: &SessionInfo,
|
||||
votes: &CandidateVotes,
|
||||
our_vote: SignedDisputeStatement,
|
||||
our_index: ValidatorIndex
|
||||
our_index: ValidatorIndex,
|
||||
) -> Result<DisputeMessage, MakeDisputeMessageError> {
|
||||
|
||||
let validators = &info.validators;
|
||||
|
||||
let (valid_statement, valid_index, invalid_statement, invalid_index) =
|
||||
if let DisputeStatement::Valid(_) = our_vote.statement() {
|
||||
let (statement_kind, validator_index, validator_signature)
|
||||
= votes.invalid.get(0).ok_or(MakeDisputeMessageError::NoOppositeVote)?.clone();
|
||||
let (statement_kind, validator_index, validator_signature) =
|
||||
votes.invalid.get(0).ok_or(MakeDisputeMessageError::NoOppositeVote)?.clone();
|
||||
let other_vote = SignedDisputeStatement::new_checked(
|
||||
DisputeStatement::Invalid(statement_kind),
|
||||
our_vote.candidate_hash().clone(),
|
||||
our_vote.session_index(),
|
||||
validators.get(validator_index.0 as usize).ok_or(MakeDisputeMessageError::InvalidValidatorIndex)?.clone(),
|
||||
validators
|
||||
.get(validator_index.0 as usize)
|
||||
.ok_or(MakeDisputeMessageError::InvalidValidatorIndex)?
|
||||
.clone(),
|
||||
validator_signature,
|
||||
).map_err(|()| MakeDisputeMessageError::InvalidStoredStatement)?;
|
||||
)
|
||||
.map_err(|()| MakeDisputeMessageError::InvalidStoredStatement)?;
|
||||
(our_vote, our_index, other_vote, validator_index)
|
||||
} else {
|
||||
let (statement_kind, validator_index, validator_signature)
|
||||
= votes.valid.get(0).ok_or(MakeDisputeMessageError::NoOppositeVote)?.clone();
|
||||
let other_vote = SignedDisputeStatement::new_checked(
|
||||
DisputeStatement::Valid(statement_kind),
|
||||
our_vote.candidate_hash().clone(),
|
||||
our_vote.session_index(),
|
||||
validators.get(validator_index.0 as usize).ok_or(MakeDisputeMessageError::InvalidValidatorIndex)?.clone(),
|
||||
validator_signature,
|
||||
).map_err(|()| MakeDisputeMessageError::InvalidStoredStatement)?;
|
||||
(other_vote, validator_index, our_vote, our_index)
|
||||
};
|
||||
} else {
|
||||
let (statement_kind, validator_index, validator_signature) =
|
||||
votes.valid.get(0).ok_or(MakeDisputeMessageError::NoOppositeVote)?.clone();
|
||||
let other_vote = SignedDisputeStatement::new_checked(
|
||||
DisputeStatement::Valid(statement_kind),
|
||||
our_vote.candidate_hash().clone(),
|
||||
our_vote.session_index(),
|
||||
validators
|
||||
.get(validator_index.0 as usize)
|
||||
.ok_or(MakeDisputeMessageError::InvalidValidatorIndex)?
|
||||
.clone(),
|
||||
validator_signature,
|
||||
)
|
||||
.map_err(|()| MakeDisputeMessageError::InvalidStoredStatement)?;
|
||||
(other_vote, validator_index, our_vote, our_index)
|
||||
};
|
||||
|
||||
DisputeMessage::from_signed_statements(
|
||||
valid_statement, valid_index,
|
||||
invalid_statement, invalid_index,
|
||||
valid_statement,
|
||||
valid_index,
|
||||
invalid_statement,
|
||||
invalid_index,
|
||||
votes.candidate_receipt.clone(),
|
||||
info,
|
||||
).map_err(MakeDisputeMessageError::InvalidStatementCombination)
|
||||
)
|
||||
.map_err(MakeDisputeMessageError::InvalidStatementCombination)
|
||||
}
|
||||
|
||||
/// Determine the the best block and its block number.
|
||||
@@ -969,7 +972,8 @@ fn determine_undisputed_chain(
|
||||
base_number: BlockNumber,
|
||||
block_descriptions: Vec<BlockDescription>,
|
||||
) -> Result<Option<(BlockNumber, Hash)>, Error> {
|
||||
let last = block_descriptions.last()
|
||||
let last = block_descriptions
|
||||
.last()
|
||||
.map(|e| (base_number + block_descriptions.len() as BlockNumber, e.block_hash));
|
||||
|
||||
// Fast path for no disputes.
|
||||
@@ -980,21 +984,20 @@ fn determine_undisputed_chain(
|
||||
};
|
||||
|
||||
let is_possibly_invalid = |session, candidate_hash| {
|
||||
recent_disputes.get(&(session, candidate_hash)).map_or(
|
||||
false,
|
||||
|status| status.is_possibly_invalid(),
|
||||
)
|
||||
recent_disputes
|
||||
.get(&(session, candidate_hash))
|
||||
.map_or(false, |status| status.is_possibly_invalid())
|
||||
};
|
||||
|
||||
for (i, BlockDescription { session, candidates, .. }) in block_descriptions.iter().enumerate() {
|
||||
if candidates.iter().any(|c| is_possibly_invalid(*session, *c)) {
|
||||
if i == 0 {
|
||||
return Ok(None);
|
||||
return Ok(None)
|
||||
} else {
|
||||
return Ok(Some((
|
||||
base_number + i as BlockNumber,
|
||||
block_descriptions[i - 1].block_hash,
|
||||
)));
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,20 +20,18 @@
|
||||
//! notified of a dispute, we recover the candidate data, validate the
|
||||
//! candidate, and cast our vote in the dispute.
|
||||
|
||||
use futures::channel::oneshot;
|
||||
use futures::prelude::*;
|
||||
use futures::{channel::oneshot, prelude::*};
|
||||
|
||||
use polkadot_node_primitives::ValidationResult;
|
||||
use polkadot_node_subsystem::{
|
||||
errors::{RecoveryError, RuntimeApiError},
|
||||
overseer,
|
||||
messages::{
|
||||
AvailabilityRecoveryMessage, AvailabilityStoreMessage,
|
||||
CandidateValidationMessage, DisputeCoordinatorMessage, DisputeParticipationMessage,
|
||||
RuntimeApiMessage, RuntimeApiRequest,
|
||||
AvailabilityRecoveryMessage, AvailabilityStoreMessage, CandidateValidationMessage,
|
||||
DisputeCoordinatorMessage, DisputeParticipationMessage, RuntimeApiMessage,
|
||||
RuntimeApiRequest,
|
||||
},
|
||||
ActiveLeavesUpdate, FromOverseer, OverseerSignal, SpawnedSubsystem,
|
||||
SubsystemContext, SubsystemError,
|
||||
overseer, ActiveLeavesUpdate, FromOverseer, OverseerSignal, SpawnedSubsystem, SubsystemContext,
|
||||
SubsystemError,
|
||||
};
|
||||
use polkadot_primitives::v1::{BlockNumber, CandidateHash, CandidateReceipt, Hash, SessionIndex};
|
||||
|
||||
@@ -64,10 +62,7 @@ where
|
||||
fn start(self, ctx: Context) -> SpawnedSubsystem {
|
||||
let future = run(ctx).map(|_| Ok(())).boxed();
|
||||
|
||||
SpawnedSubsystem {
|
||||
name: "dispute-participation-subsystem",
|
||||
future,
|
||||
}
|
||||
SpawnedSubsystem { name: "dispute-participation-subsystem", future }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +101,7 @@ impl Error {
|
||||
// don't spam the log with spurious errors
|
||||
Self::RuntimeApi(_) | Self::Oneshot(_) => {
|
||||
tracing::debug!(target: LOG_TARGET, err = ?self)
|
||||
}
|
||||
},
|
||||
// it's worth reporting otherwise
|
||||
_ => tracing::warn!(target: LOG_TARGET, err = ?self),
|
||||
}
|
||||
@@ -125,20 +120,20 @@ where
|
||||
Err(_) => return,
|
||||
Ok(FromOverseer::Signal(OverseerSignal::Conclude)) => {
|
||||
tracing::info!(target: LOG_TARGET, "Received `Conclude` signal, exiting");
|
||||
return;
|
||||
}
|
||||
Ok(FromOverseer::Signal(OverseerSignal::BlockFinalized(_, _))) => {}
|
||||
return
|
||||
},
|
||||
Ok(FromOverseer::Signal(OverseerSignal::BlockFinalized(_, _))) => {},
|
||||
Ok(FromOverseer::Signal(OverseerSignal::ActiveLeaves(update))) => {
|
||||
update_state(&mut state, update);
|
||||
}
|
||||
},
|
||||
Ok(FromOverseer::Communication { msg }) => {
|
||||
if let Err(err) = handle_incoming(&mut ctx, &mut state, msg).await {
|
||||
err.trace();
|
||||
if let Error::Subsystem(SubsystemError::Context(_)) = err {
|
||||
return;
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,7 +158,7 @@ async fn handle_incoming(
|
||||
session,
|
||||
n_validators,
|
||||
report_availability,
|
||||
} => {
|
||||
} =>
|
||||
if let Some((_, block_hash)) = state.recent_block {
|
||||
participate(
|
||||
ctx,
|
||||
@@ -176,9 +171,8 @@ async fn handle_incoming(
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
return Err(ParticipationError::MissingRecentBlockState.into());
|
||||
}
|
||||
}
|
||||
return Err(ParticipationError::MissingRecentBlockState.into())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,47 +192,43 @@ async fn participate(
|
||||
|
||||
// in order to validate a candidate we need to start by recovering the
|
||||
// available data
|
||||
ctx.send_message(
|
||||
AvailabilityRecoveryMessage::RecoverAvailableData(
|
||||
candidate_receipt.clone(),
|
||||
session,
|
||||
None,
|
||||
recover_available_data_tx,
|
||||
)
|
||||
)
|
||||
ctx.send_message(AvailabilityRecoveryMessage::RecoverAvailableData(
|
||||
candidate_receipt.clone(),
|
||||
session,
|
||||
None,
|
||||
recover_available_data_tx,
|
||||
))
|
||||
.await;
|
||||
|
||||
let available_data = match recover_available_data_rx.await? {
|
||||
Ok(data) => {
|
||||
report_availability.send(true).map_err(|_| Error::OneshotSendFailed)?;
|
||||
data
|
||||
}
|
||||
},
|
||||
Err(RecoveryError::Invalid) => {
|
||||
report_availability.send(true).map_err(|_| Error::OneshotSendFailed)?;
|
||||
|
||||
// the available data was recovered but it is invalid, therefore we'll
|
||||
// vote negatively for the candidate dispute
|
||||
cast_invalid_vote(ctx, candidate_hash, candidate_receipt, session).await;
|
||||
return Ok(());
|
||||
}
|
||||
return Ok(())
|
||||
},
|
||||
Err(RecoveryError::Unavailable) => {
|
||||
report_availability.send(false).map_err(|_| Error::OneshotSendFailed)?;
|
||||
|
||||
return Err(ParticipationError::MissingAvailableData(candidate_hash).into());
|
||||
}
|
||||
return Err(ParticipationError::MissingAvailableData(candidate_hash).into())
|
||||
},
|
||||
};
|
||||
|
||||
// we also need to fetch the validation code which we can reference by its
|
||||
// hash as taken from the candidate descriptor
|
||||
ctx.send_message(
|
||||
RuntimeApiMessage::Request(
|
||||
block_hash,
|
||||
RuntimeApiRequest::ValidationCodeByHash(
|
||||
candidate_receipt.descriptor.validation_code_hash,
|
||||
code_tx,
|
||||
),
|
||||
)
|
||||
)
|
||||
ctx.send_message(RuntimeApiMessage::Request(
|
||||
block_hash,
|
||||
RuntimeApiRequest::ValidationCodeByHash(
|
||||
candidate_receipt.descriptor.validation_code_hash,
|
||||
code_tx,
|
||||
),
|
||||
))
|
||||
.await;
|
||||
|
||||
let validation_code = match code_rx.await?? {
|
||||
@@ -251,22 +241,20 @@ async fn participate(
|
||||
block_hash,
|
||||
);
|
||||
|
||||
return Err(ParticipationError::MissingValidationCode(candidate_hash).into());
|
||||
}
|
||||
return Err(ParticipationError::MissingValidationCode(candidate_hash).into())
|
||||
},
|
||||
};
|
||||
|
||||
// we dispatch a request to store the available data for the candidate. we
|
||||
// want to maximize data availability for other potential checkers involved
|
||||
// in the dispute
|
||||
ctx.send_message(
|
||||
AvailabilityStoreMessage::StoreAvailableData(
|
||||
candidate_hash,
|
||||
None,
|
||||
n_validators,
|
||||
available_data.clone(),
|
||||
store_available_data_tx,
|
||||
)
|
||||
)
|
||||
ctx.send_message(AvailabilityStoreMessage::StoreAvailableData(
|
||||
candidate_hash,
|
||||
None,
|
||||
n_validators,
|
||||
available_data.clone(),
|
||||
store_available_data_tx,
|
||||
))
|
||||
.await;
|
||||
|
||||
match store_available_data_rx.await? {
|
||||
@@ -276,21 +264,19 @@ async fn participate(
|
||||
"Failed to store available data for candidate {:?}",
|
||||
candidate_hash,
|
||||
);
|
||||
}
|
||||
Ok(()) => {}
|
||||
},
|
||||
Ok(()) => {},
|
||||
}
|
||||
|
||||
// we issue a request to validate the candidate with the provided exhaustive
|
||||
// parameters
|
||||
ctx.send_message(
|
||||
CandidateValidationMessage::ValidateFromExhaustive(
|
||||
available_data.validation_data,
|
||||
validation_code,
|
||||
candidate_receipt.descriptor.clone(),
|
||||
available_data.pov,
|
||||
validation_tx,
|
||||
)
|
||||
)
|
||||
ctx.send_message(CandidateValidationMessage::ValidateFromExhaustive(
|
||||
available_data.validation_data,
|
||||
validation_code,
|
||||
candidate_receipt.descriptor.clone(),
|
||||
available_data.pov,
|
||||
validation_tx,
|
||||
))
|
||||
.await;
|
||||
|
||||
// we cast votes (either positive or negative) depending on the outcome of
|
||||
@@ -305,7 +291,7 @@ async fn participate(
|
||||
);
|
||||
|
||||
cast_invalid_vote(ctx, candidate_hash, candidate_receipt, session).await;
|
||||
}
|
||||
},
|
||||
Ok(ValidationResult::Invalid(invalid)) => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
@@ -315,7 +301,7 @@ async fn participate(
|
||||
);
|
||||
|
||||
cast_invalid_vote(ctx, candidate_hash, candidate_receipt, session).await;
|
||||
}
|
||||
},
|
||||
Ok(ValidationResult::Valid(commitments, _)) => {
|
||||
if commitments.hash() != candidate_receipt.commitments_hash {
|
||||
tracing::warn!(
|
||||
@@ -329,7 +315,7 @@ async fn participate(
|
||||
} else {
|
||||
cast_valid_vote(ctx, candidate_hash, candidate_receipt, session).await;
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -372,13 +358,11 @@ async fn issue_local_statement(
|
||||
session: SessionIndex,
|
||||
valid: bool,
|
||||
) {
|
||||
ctx.send_message(
|
||||
DisputeCoordinatorMessage::IssueLocalStatement(
|
||||
session,
|
||||
candidate_hash,
|
||||
candidate_receipt,
|
||||
valid,
|
||||
),
|
||||
)
|
||||
ctx.send_message(DisputeCoordinatorMessage::IssueLocalStatement(
|
||||
session,
|
||||
candidate_hash,
|
||||
candidate_receipt,
|
||||
valid,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -24,8 +24,10 @@ use super::*;
|
||||
use parity_scale_codec::Encode;
|
||||
use polkadot_node_primitives::{AvailableData, BlockData, InvalidCandidate, PoV};
|
||||
use polkadot_node_subsystem::{
|
||||
jaeger,
|
||||
messages::{AllMessages, ValidationFailed},
|
||||
overseer::Subsystem,
|
||||
jaeger, messages::{AllMessages, ValidationFailed}, ActivatedLeaf, ActiveLeavesUpdate, LeafStatus,
|
||||
ActivatedLeaf, ActiveLeavesUpdate, LeafStatus,
|
||||
};
|
||||
use polkadot_node_subsystem_test_helpers::{make_subsystem_context, TestSubsystemContextHandle};
|
||||
use polkadot_primitives::v1::{BlakeTwo256, CandidateCommitments, HashT, Header, ValidationCode};
|
||||
@@ -45,9 +47,7 @@ where
|
||||
let (subsystem_result, _) =
|
||||
futures::executor::block_on(future::join(spawned_subsystem.future, async move {
|
||||
let mut ctx_handle = test_future.await;
|
||||
ctx_handle
|
||||
.send(FromOverseer::Signal(OverseerSignal::Conclude))
|
||||
.await;
|
||||
ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await;
|
||||
|
||||
// no further request is received by the overseer which means that
|
||||
// no further attempt to participate was made
|
||||
@@ -69,14 +69,14 @@ async fn activate_leaf(virtual_overseer: &mut VirtualOverseer, block_number: Blo
|
||||
let block_hash = block_header.hash();
|
||||
|
||||
virtual_overseer
|
||||
.send(FromOverseer::Signal(OverseerSignal::ActiveLeaves(
|
||||
ActiveLeavesUpdate::start_work(ActivatedLeaf {
|
||||
.send(FromOverseer::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(
|
||||
ActivatedLeaf {
|
||||
hash: block_hash,
|
||||
span: Arc::new(jaeger::Span::Disabled),
|
||||
number: block_number,
|
||||
status: LeafStatus::Fresh,
|
||||
}),
|
||||
)))
|
||||
},
|
||||
))))
|
||||
.await;
|
||||
}
|
||||
|
||||
@@ -102,20 +102,19 @@ async fn participate(virtual_overseer: &mut VirtualOverseer) -> oneshot::Receive
|
||||
n_validators,
|
||||
report_availability,
|
||||
},
|
||||
})
|
||||
.await;
|
||||
})
|
||||
.await;
|
||||
receive_availability
|
||||
}
|
||||
|
||||
async fn recover_available_data(virtual_overseer: &mut VirtualOverseer, receive_availability: oneshot::Receiver<bool>) {
|
||||
let pov_block = PoV {
|
||||
block_data: BlockData(Vec::new()),
|
||||
};
|
||||
async fn recover_available_data(
|
||||
virtual_overseer: &mut VirtualOverseer,
|
||||
receive_availability: oneshot::Receiver<bool>,
|
||||
) {
|
||||
let pov_block = PoV { block_data: BlockData(Vec::new()) };
|
||||
|
||||
let available_data = AvailableData {
|
||||
pov: Arc::new(pov_block),
|
||||
validation_data: Default::default(),
|
||||
};
|
||||
let available_data =
|
||||
AvailableData { pov: Arc::new(pov_block), validation_data: Default::default() };
|
||||
|
||||
assert_matches!(
|
||||
virtual_overseer.recv().await,
|
||||
@@ -217,7 +216,10 @@ fn cannot_participate_if_cannot_recover_available_data() {
|
||||
"overseer did not receive recover available data message",
|
||||
);
|
||||
|
||||
assert_eq!(receive_availability.await.expect("Availability should get reported"), false);
|
||||
assert_eq!(
|
||||
receive_availability.await.expect("Availability should get reported"),
|
||||
false
|
||||
);
|
||||
|
||||
virtual_overseer
|
||||
})
|
||||
|
||||
@@ -26,12 +26,9 @@
|
||||
|
||||
use futures::{select, FutureExt};
|
||||
use polkadot_node_subsystem::{
|
||||
overseer::Handle,
|
||||
messages::ProvisionerMessage, errors::SubsystemError,
|
||||
};
|
||||
use polkadot_primitives::v1::{
|
||||
Block, Hash, InherentData as ParachainsInherentData,
|
||||
errors::SubsystemError, messages::ProvisionerMessage, overseer::Handle,
|
||||
};
|
||||
use polkadot_primitives::v1::{Block, Hash, InherentData as ParachainsInherentData};
|
||||
use sp_blockchain::HeaderBackend;
|
||||
use sp_runtime::generic::BlockId;
|
||||
use std::time;
|
||||
@@ -54,13 +51,18 @@ impl ParachainsInherentDataProvider {
|
||||
let pid = async {
|
||||
let (sender, receiver) = futures::channel::oneshot::channel();
|
||||
overseer.wait_for_activation(parent, sender).await;
|
||||
receiver.await.map_err(|_| Error::ClosedChannelAwaitingActivation)?.map_err(|e| Error::Subsystem(e))?;
|
||||
receiver
|
||||
.await
|
||||
.map_err(|_| Error::ClosedChannelAwaitingActivation)?
|
||||
.map_err(|e| Error::Subsystem(e))?;
|
||||
|
||||
let (sender, receiver) = futures::channel::oneshot::channel();
|
||||
overseer.send_msg(
|
||||
ProvisionerMessage::RequestInherentData(parent, sender),
|
||||
std::any::type_name::<Self>(),
|
||||
).await;
|
||||
overseer
|
||||
.send_msg(
|
||||
ProvisionerMessage::RequestInherentData(parent, sender),
|
||||
std::any::type_name::<Self>(),
|
||||
)
|
||||
.await;
|
||||
|
||||
receiver.await.map_err(|_| Error::ClosedChannelAwaitingInherentData)
|
||||
};
|
||||
@@ -96,7 +98,7 @@ impl ParachainsInherentDataProvider {
|
||||
disputes: Vec::new(),
|
||||
parent_header,
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Ok(Self { inherent_data })
|
||||
@@ -105,11 +107,12 @@ impl ParachainsInherentDataProvider {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl sp_inherents::InherentDataProvider for ParachainsInherentDataProvider {
|
||||
fn provide_inherent_data(&self, inherent_data: &mut sp_inherents::InherentData) -> Result<(), sp_inherents::Error> {
|
||||
inherent_data.put_data(
|
||||
polkadot_primitives::v1::PARACHAINS_INHERENT_IDENTIFIER,
|
||||
&self.inherent_data,
|
||||
)
|
||||
fn provide_inherent_data(
|
||||
&self,
|
||||
inherent_data: &mut sp_inherents::InherentData,
|
||||
) -> Result<(), sp_inherents::Error> {
|
||||
inherent_data
|
||||
.put_data(polkadot_primitives::v1::PARACHAINS_INHERENT_IDENTIFIER, &self.inherent_data)
|
||||
}
|
||||
|
||||
async fn try_handle_error(
|
||||
|
||||
@@ -24,25 +24,29 @@ use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
prelude::*,
|
||||
};
|
||||
use futures_timer::Delay;
|
||||
use polkadot_node_subsystem::{
|
||||
errors::{ChainApiError, RuntimeApiError}, PerLeafSpan, SubsystemSender, jaeger,
|
||||
errors::{ChainApiError, RuntimeApiError},
|
||||
jaeger,
|
||||
messages::{
|
||||
CandidateBackingMessage, ChainApiMessage, ProvisionableData, ProvisionerInherentData,
|
||||
ProvisionerMessage, DisputeCoordinatorMessage,
|
||||
CandidateBackingMessage, ChainApiMessage, DisputeCoordinatorMessage, ProvisionableData,
|
||||
ProvisionerInherentData, ProvisionerMessage,
|
||||
},
|
||||
PerLeafSpan, SubsystemSender,
|
||||
};
|
||||
use polkadot_node_subsystem_util::{
|
||||
self as util, JobSubsystem, JobSender,
|
||||
request_availability_cores, request_persisted_validation_data, JobTrait, metrics::{self, prometheus},
|
||||
self as util,
|
||||
metrics::{self, prometheus},
|
||||
request_availability_cores, request_persisted_validation_data, JobSender, JobSubsystem,
|
||||
JobTrait,
|
||||
};
|
||||
use polkadot_primitives::v1::{
|
||||
BackedCandidate, BlockNumber, CandidateReceipt, CoreState, Hash, OccupiedCoreAssumption,
|
||||
SignedAvailabilityBitfield, ValidatorIndex, MultiDisputeStatementSet, DisputeStatementSet,
|
||||
DisputeStatement,
|
||||
BackedCandidate, BlockNumber, CandidateReceipt, CoreState, DisputeStatement,
|
||||
DisputeStatementSet, Hash, MultiDisputeStatementSet, OccupiedCoreAssumption,
|
||||
SignedAvailabilityBitfield, ValidatorIndex,
|
||||
};
|
||||
use std::{pin::Pin, collections::BTreeMap, sync::Arc};
|
||||
use std::{collections::BTreeMap, pin::Pin, sync::Arc};
|
||||
use thiserror::Error;
|
||||
use futures_timer::Delay;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -92,7 +96,7 @@ pub struct ProvisioningJob {
|
||||
signed_bitfields: Vec<SignedAvailabilityBitfield>,
|
||||
metrics: Metrics,
|
||||
inherent_after: InherentAfter,
|
||||
awaiting_inherent: Vec<oneshot::Sender<ProvisionerInherentData>>
|
||||
awaiting_inherent: Vec<oneshot::Sender<ProvisionerInherentData>>,
|
||||
}
|
||||
|
||||
/// Errors in the provisioner.
|
||||
@@ -132,7 +136,9 @@ pub enum Error {
|
||||
#[error("failed to send return message with Inherents")]
|
||||
InherentDataReturnChannel,
|
||||
|
||||
#[error("backed candidate does not correspond to selected candidate; check logic in provisioner")]
|
||||
#[error(
|
||||
"backed candidate does not correspond to selected candidate; check logic in provisioner"
|
||||
)]
|
||||
BackedCandidateOrderingProblem,
|
||||
}
|
||||
|
||||
@@ -156,13 +162,10 @@ impl JobTrait for ProvisioningJob {
|
||||
mut sender: JobSender<S>,
|
||||
) -> Pin<Box<dyn Future<Output = Result<(), Self::Error>> + Send>> {
|
||||
async move {
|
||||
let job = ProvisioningJob::new(
|
||||
relay_parent,
|
||||
metrics,
|
||||
receiver,
|
||||
);
|
||||
let job = ProvisioningJob::new(relay_parent, metrics, receiver);
|
||||
|
||||
job.run_loop(sender.subsystem_sender(), PerLeafSpan::new(span, "provisioner")).await
|
||||
job.run_loop(sender.subsystem_sender(), PerLeafSpan::new(span, "provisioner"))
|
||||
.await
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
@@ -190,9 +193,7 @@ impl ProvisioningJob {
|
||||
sender: &mut impl SubsystemSender,
|
||||
span: PerLeafSpan,
|
||||
) -> Result<(), Error> {
|
||||
use ProvisionerMessage::{
|
||||
ProvisionableData, RequestInherentData,
|
||||
};
|
||||
use ProvisionerMessage::{ProvisionableData, RequestInherentData};
|
||||
loop {
|
||||
futures::select! {
|
||||
msg = self.receiver.next() => match msg {
|
||||
@@ -248,17 +249,21 @@ impl ProvisioningJob {
|
||||
}
|
||||
}
|
||||
|
||||
fn note_provisionable_data(&mut self, span: &jaeger::Span, provisionable_data: ProvisionableData) {
|
||||
fn note_provisionable_data(
|
||||
&mut self,
|
||||
span: &jaeger::Span,
|
||||
provisionable_data: ProvisionableData,
|
||||
) {
|
||||
match provisionable_data {
|
||||
ProvisionableData::Bitfield(_, signed_bitfield) => {
|
||||
self.signed_bitfields.push(signed_bitfield)
|
||||
}
|
||||
ProvisionableData::Bitfield(_, signed_bitfield) =>
|
||||
self.signed_bitfields.push(signed_bitfield),
|
||||
ProvisionableData::BackedCandidate(backed_candidate) => {
|
||||
let _span = span.child("provisionable-backed")
|
||||
let _span = span
|
||||
.child("provisionable-backed")
|
||||
.with_para_id(backed_candidate.descriptor().para_id);
|
||||
self.backed_candidates.push(backed_candidate)
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -291,27 +296,23 @@ async fn send_inherent_data(
|
||||
) -> Result<(), Error> {
|
||||
let availability_cores = request_availability_cores(relay_parent, from_job)
|
||||
.await
|
||||
.await.map_err(|err| Error::CanceledAvailabilityCores(err))??;
|
||||
.await
|
||||
.map_err(|err| Error::CanceledAvailabilityCores(err))??;
|
||||
|
||||
let bitfields = select_availability_bitfields(&availability_cores, bitfields);
|
||||
let candidates = select_candidates(
|
||||
&availability_cores,
|
||||
&bitfields,
|
||||
candidates,
|
||||
relay_parent,
|
||||
from_job,
|
||||
).await?;
|
||||
let candidates =
|
||||
select_candidates(&availability_cores, &bitfields, candidates, relay_parent, from_job)
|
||||
.await?;
|
||||
|
||||
let disputes = select_disputes(from_job).await?;
|
||||
|
||||
let inherent_data = ProvisionerInherentData {
|
||||
bitfields,
|
||||
backed_candidates: candidates,
|
||||
disputes,
|
||||
};
|
||||
let inherent_data =
|
||||
ProvisionerInherentData { bitfields, backed_candidates: candidates, disputes };
|
||||
|
||||
for return_sender in return_senders {
|
||||
return_sender.send(inherent_data.clone()).map_err(|_data| Error::InherentDataReturnChannel)?;
|
||||
return_sender
|
||||
.send(inherent_data.clone())
|
||||
.map_err(|_data| Error::InherentDataReturnChannel)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -333,16 +334,18 @@ fn select_availability_bitfields(
|
||||
) -> Vec<SignedAvailabilityBitfield> {
|
||||
let mut selected: BTreeMap<ValidatorIndex, SignedAvailabilityBitfield> = BTreeMap::new();
|
||||
|
||||
'a:
|
||||
for bitfield in bitfields.iter().cloned() {
|
||||
'a: for bitfield in bitfields.iter().cloned() {
|
||||
if bitfield.payload().0.len() != cores.len() {
|
||||
continue
|
||||
}
|
||||
|
||||
let is_better = selected.get(&bitfield.validator_index())
|
||||
let is_better = selected
|
||||
.get(&bitfield.validator_index())
|
||||
.map_or(true, |b| b.payload().0.count_ones() < bitfield.payload().0.count_ones());
|
||||
|
||||
if !is_better { continue }
|
||||
if !is_better {
|
||||
continue
|
||||
}
|
||||
|
||||
for (idx, _) in cores.iter().enumerate().filter(|v| !v.1.is_occupied()) {
|
||||
// Bit is set for an unoccupied core - invalid
|
||||
@@ -374,23 +377,24 @@ async fn select_candidates(
|
||||
let (scheduled_core, assumption) = match core {
|
||||
CoreState::Scheduled(scheduled_core) => (scheduled_core, OccupiedCoreAssumption::Free),
|
||||
CoreState::Occupied(occupied_core) => {
|
||||
if bitfields_indicate_availability(core_idx, bitfields, &occupied_core.availability) {
|
||||
if bitfields_indicate_availability(core_idx, bitfields, &occupied_core.availability)
|
||||
{
|
||||
if let Some(ref scheduled_core) = occupied_core.next_up_on_available {
|
||||
(scheduled_core, OccupiedCoreAssumption::Included)
|
||||
} else {
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if occupied_core.time_out_at != block_number {
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
if let Some(ref scheduled_core) = occupied_core.next_up_on_time_out {
|
||||
(scheduled_core, OccupiedCoreAssumption::TimedOut)
|
||||
} else {
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
CoreState::Free => continue,
|
||||
};
|
||||
|
||||
@@ -401,7 +405,8 @@ async fn select_candidates(
|
||||
sender,
|
||||
)
|
||||
.await
|
||||
.await.map_err(|err| Error::CanceledPersistedValidationData(err))??
|
||||
.await
|
||||
.map_err(|err| Error::CanceledPersistedValidationData(err))??
|
||||
{
|
||||
Some(v) => v,
|
||||
None => continue,
|
||||
@@ -412,8 +417,8 @@ async fn select_candidates(
|
||||
// we arbitrarily pick the first of the backed candidates which match the appropriate selection criteria
|
||||
if let Some(candidate) = candidates.iter().find(|backed_candidate| {
|
||||
let descriptor = &backed_candidate.descriptor;
|
||||
descriptor.para_id == scheduled_core.para_id
|
||||
&& descriptor.persisted_validation_data_hash == computed_validation_data_hash
|
||||
descriptor.para_id == scheduled_core.para_id &&
|
||||
descriptor.persisted_validation_data_hash == computed_validation_data_hash
|
||||
}) {
|
||||
let candidate_hash = candidate.hash();
|
||||
tracing::trace!(
|
||||
@@ -430,11 +435,16 @@ async fn select_candidates(
|
||||
|
||||
// now get the backed candidates corresponding to these candidate receipts
|
||||
let (tx, rx) = oneshot::channel();
|
||||
sender.send_message(CandidateBackingMessage::GetBackedCandidates(
|
||||
relay_parent,
|
||||
selected_candidates.clone(),
|
||||
tx,
|
||||
).into()).await;
|
||||
sender
|
||||
.send_message(
|
||||
CandidateBackingMessage::GetBackedCandidates(
|
||||
relay_parent,
|
||||
selected_candidates.clone(),
|
||||
tx,
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
.await;
|
||||
let mut candidates = rx.await.map_err(|err| Error::CanceledBackedCandidates(err))?;
|
||||
|
||||
// `selected_candidates` is generated in ascending order by core index, and `GetBackedCandidates`
|
||||
@@ -445,7 +455,9 @@ async fn select_candidates(
|
||||
// in order, we can ensure that the backed candidates are also in order.
|
||||
let mut backed_idx = 0;
|
||||
for selected in selected_candidates {
|
||||
if selected == candidates.get(backed_idx).ok_or(Error::BackedCandidateOrderingProblem)?.hash() {
|
||||
if selected ==
|
||||
candidates.get(backed_idx).ok_or(Error::BackedCandidateOrderingProblem)?.hash()
|
||||
{
|
||||
backed_idx += 1;
|
||||
}
|
||||
}
|
||||
@@ -484,12 +496,7 @@ async fn get_block_number_under_construction(
|
||||
sender: &mut impl SubsystemSender,
|
||||
) -> Result<BlockNumber, Error> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
sender
|
||||
.send_message(ChainApiMessage::BlockNumber(
|
||||
relay_parent,
|
||||
tx,
|
||||
).into())
|
||||
.await;
|
||||
sender.send_message(ChainApiMessage::BlockNumber(relay_parent, tx).into()).await;
|
||||
|
||||
match rx.await.map_err(|err| Error::CanceledBlockNumber(err))? {
|
||||
Ok(Some(n)) => Ok(n + 1),
|
||||
@@ -528,8 +535,8 @@ fn bitfields_indicate_availability(
|
||||
availability_len,
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
return false
|
||||
},
|
||||
Some(mut bit_mut) => *bit_mut |= bitfield.payload().0[core_idx],
|
||||
}
|
||||
}
|
||||
@@ -562,16 +569,17 @@ async fn select_disputes(
|
||||
);
|
||||
|
||||
Vec::new()
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Load all votes for all disputes from the coordinator.
|
||||
let dispute_candidate_votes = {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
sender.send_message(DisputeCoordinatorMessage::QueryCandidateVotes(
|
||||
recent_disputes,
|
||||
tx,
|
||||
).into()).await;
|
||||
sender
|
||||
.send_message(
|
||||
DisputeCoordinatorMessage::QueryCandidateVotes(recent_disputes, tx).into(),
|
||||
)
|
||||
.await;
|
||||
|
||||
match rx.await {
|
||||
Ok(v) => v,
|
||||
@@ -581,24 +589,29 @@ async fn select_disputes(
|
||||
"Unable to query candidate votes - subsystem disconnected?",
|
||||
);
|
||||
Vec::new()
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// Transform all `CandidateVotes` into `MultiDisputeStatementSet`.
|
||||
Ok(dispute_candidate_votes.into_iter().map(|(session_index, candidate_hash, votes)| {
|
||||
let valid_statements = votes.valid.into_iter()
|
||||
.map(|(s, i, sig)| (DisputeStatement::Valid(s), i, sig));
|
||||
Ok(dispute_candidate_votes
|
||||
.into_iter()
|
||||
.map(|(session_index, candidate_hash, votes)| {
|
||||
let valid_statements =
|
||||
votes.valid.into_iter().map(|(s, i, sig)| (DisputeStatement::Valid(s), i, sig));
|
||||
|
||||
let invalid_statements = votes.invalid.into_iter()
|
||||
.map(|(s, i, sig)| (DisputeStatement::Invalid(s), i, sig));
|
||||
let invalid_statements = votes
|
||||
.invalid
|
||||
.into_iter()
|
||||
.map(|(s, i, sig)| (DisputeStatement::Invalid(s), i, sig));
|
||||
|
||||
DisputeStatementSet {
|
||||
candidate_hash,
|
||||
session: session_index,
|
||||
statements: valid_statements.chain(invalid_statements).collect(),
|
||||
}
|
||||
}).collect())
|
||||
DisputeStatementSet {
|
||||
candidate_hash,
|
||||
session: session_index,
|
||||
statements: valid_statements.chain(invalid_statements).collect(),
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -623,7 +636,9 @@ impl Metrics {
|
||||
}
|
||||
|
||||
/// Provide a timer for `request_inherent_data` which observes on drop.
|
||||
fn time_request_inherent_data(&self) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
|
||||
fn time_request_inherent_data(
|
||||
&self,
|
||||
) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
|
||||
self.0.as_ref().map(|metrics| metrics.request_inherent_data.start_timer())
|
||||
}
|
||||
|
||||
@@ -647,21 +662,17 @@ impl metrics::Metrics for Metrics {
|
||||
registry,
|
||||
)?,
|
||||
request_inherent_data: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_provisioner_request_inherent_data",
|
||||
"Time spent within `provisioner::request_inherent_data`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_provisioner_request_inherent_data",
|
||||
"Time spent within `provisioner::request_inherent_data`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
provisionable_data: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_provisioner_provisionable_data",
|
||||
"Time spent within `provisioner::provisionable_data`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_provisioner_provisionable_data",
|
||||
"Time spent within `provisioner::provisionable_data`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
};
|
||||
@@ -669,6 +680,5 @@ impl metrics::Metrics for Metrics {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The provisioning subsystem.
|
||||
pub type ProvisioningSubsystem<Spawner> = JobSubsystem<ProvisioningJob, Spawner>;
|
||||
|
||||
@@ -34,20 +34,16 @@ pub fn default_bitvec(n_cores: usize) -> CoreAvailability {
|
||||
}
|
||||
|
||||
pub fn scheduled_core(id: u32) -> ScheduledCore {
|
||||
ScheduledCore {
|
||||
para_id: id.into(),
|
||||
..Default::default()
|
||||
}
|
||||
ScheduledCore { para_id: id.into(), ..Default::default() }
|
||||
}
|
||||
|
||||
mod select_availability_bitfields {
|
||||
use super::super::*;
|
||||
use super::{default_bitvec, occupied_core};
|
||||
use super::{super::*, default_bitvec, occupied_core};
|
||||
use futures::executor::block_on;
|
||||
use std::sync::Arc;
|
||||
use polkadot_primitives::v1::{SigningContext, ValidatorIndex, ValidatorId};
|
||||
use polkadot_primitives::v1::{SigningContext, ValidatorId, ValidatorIndex};
|
||||
use sp_application_crypto::AppKey;
|
||||
use sp_keystore::{CryptoStore, SyncCryptoStorePtr, testing::KeyStore};
|
||||
use sp_keystore::{testing::KeyStore, CryptoStore, SyncCryptoStorePtr};
|
||||
use std::sync::Arc;
|
||||
|
||||
async fn signed_bitfield(
|
||||
keystore: &SyncCryptoStorePtr,
|
||||
@@ -63,7 +59,11 @@ mod select_availability_bitfields {
|
||||
&<SigningContext<Hash>>::default(),
|
||||
validator_idx,
|
||||
&public.into(),
|
||||
).await.ok().flatten().expect("Should be signed")
|
||||
)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.expect("Should be signed")
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -109,11 +109,8 @@ mod select_availability_bitfields {
|
||||
let mut bitvec2 = bitvec.clone();
|
||||
bitvec2.set(2, true);
|
||||
|
||||
let cores = vec![
|
||||
CoreState::Free,
|
||||
CoreState::Scheduled(Default::default()),
|
||||
occupied_core(2),
|
||||
];
|
||||
let cores =
|
||||
vec![CoreState::Free, CoreState::Scheduled(Default::default()), occupied_core(2)];
|
||||
|
||||
let bitfields = vec![
|
||||
block_on(signed_bitfield(&keystore, bitvec0, ValidatorIndex(0))),
|
||||
@@ -191,16 +188,18 @@ mod select_availability_bitfields {
|
||||
}
|
||||
|
||||
mod select_candidates {
|
||||
use super::super::*;
|
||||
use super::{build_occupied_core, occupied_core, scheduled_core, default_bitvec};
|
||||
use super::{super::*, build_occupied_core, default_bitvec, occupied_core, scheduled_core};
|
||||
use polkadot_node_subsystem::messages::{
|
||||
AllMessages, RuntimeApiMessage,
|
||||
RuntimeApiRequest::{AvailabilityCores, PersistedValidationData as PersistedValidationDataReq},
|
||||
};
|
||||
use polkadot_primitives::v1::{
|
||||
BlockNumber, CandidateDescriptor, PersistedValidationData, CommittedCandidateReceipt, CandidateCommitments,
|
||||
RuntimeApiRequest::{
|
||||
AvailabilityCores, PersistedValidationData as PersistedValidationDataReq,
|
||||
},
|
||||
};
|
||||
use polkadot_node_subsystem_test_helpers::TestSubsystemSender;
|
||||
use polkadot_primitives::v1::{
|
||||
BlockNumber, CandidateCommitments, CandidateDescriptor, CommittedCandidateReceipt,
|
||||
PersistedValidationData,
|
||||
};
|
||||
|
||||
const BLOCK_UNDER_PRODUCTION: BlockNumber = 128;
|
||||
|
||||
@@ -307,21 +306,21 @@ mod select_candidates {
|
||||
|
||||
while let Some(from_job) = receiver.next().await {
|
||||
match from_job {
|
||||
AllMessages::ChainApi(BlockNumber(_relay_parent, tx)) => {
|
||||
tx.send(Ok(Some(BLOCK_UNDER_PRODUCTION - 1))).unwrap()
|
||||
}
|
||||
AllMessages::ChainApi(BlockNumber(_relay_parent, tx)) =>
|
||||
tx.send(Ok(Some(BLOCK_UNDER_PRODUCTION - 1))).unwrap(),
|
||||
AllMessages::RuntimeApi(Request(
|
||||
_parent_hash,
|
||||
PersistedValidationDataReq(_para_id, _assumption, tx),
|
||||
)) => tx.send(Ok(Some(Default::default()))).unwrap(),
|
||||
AllMessages::RuntimeApi(Request(_parent_hash, AvailabilityCores(tx))) => {
|
||||
tx.send(Ok(mock_availability_cores())).unwrap()
|
||||
}
|
||||
AllMessages::CandidateBacking(
|
||||
CandidateBackingMessage::GetBackedCandidates(_, _, sender)
|
||||
) => {
|
||||
AllMessages::RuntimeApi(Request(_parent_hash, AvailabilityCores(tx))) =>
|
||||
tx.send(Ok(mock_availability_cores())).unwrap(),
|
||||
AllMessages::CandidateBacking(CandidateBackingMessage::GetBackedCandidates(
|
||||
_,
|
||||
_,
|
||||
sender,
|
||||
)) => {
|
||||
let _ = sender.send(expected.clone());
|
||||
}
|
||||
},
|
||||
_ => panic!("Unexpected message: {:?}", from_job),
|
||||
}
|
||||
}
|
||||
@@ -329,9 +328,12 @@ mod select_candidates {
|
||||
|
||||
#[test]
|
||||
fn can_succeed() {
|
||||
test_harness(|r| mock_overseer(r, Vec::new()), |mut tx: TestSubsystemSender| async move {
|
||||
select_candidates(&[], &[], &[], Default::default(), &mut tx).await.unwrap();
|
||||
})
|
||||
test_harness(
|
||||
|r| mock_overseer(r, Vec::new()),
|
||||
|mut tx: TestSubsystemSender| async move {
|
||||
select_candidates(&[], &[], &[], Default::default(), &mut tx).await.unwrap();
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// this tests that only the appropriate candidates get selected.
|
||||
@@ -368,8 +370,7 @@ mod select_candidates {
|
||||
candidate
|
||||
} else if idx < mock_cores.len() * 2 {
|
||||
// for the second repetition of the candidates, give them the wrong hash
|
||||
candidate.descriptor.persisted_validation_data_hash
|
||||
= Default::default();
|
||||
candidate.descriptor.persisted_validation_data_hash = Default::default();
|
||||
candidate
|
||||
} else {
|
||||
// third go-around: right hash, wrong para_id
|
||||
@@ -380,34 +381,38 @@ mod select_candidates {
|
||||
.collect();
|
||||
|
||||
// why those particular indices? see the comments on mock_availability_cores()
|
||||
let expected_candidates: Vec<_> = [1, 4, 7, 8, 10]
|
||||
.iter()
|
||||
.map(|&idx| candidates[idx].clone())
|
||||
.collect();
|
||||
let expected_candidates: Vec<_> =
|
||||
[1, 4, 7, 8, 10].iter().map(|&idx| candidates[idx].clone()).collect();
|
||||
|
||||
let expected_backed = expected_candidates
|
||||
.iter()
|
||||
.map(|c| BackedCandidate {
|
||||
candidate: CommittedCandidateReceipt { descriptor: c.descriptor.clone(), ..Default::default() },
|
||||
candidate: CommittedCandidateReceipt {
|
||||
descriptor: c.descriptor.clone(),
|
||||
..Default::default()
|
||||
},
|
||||
validity_votes: Vec::new(),
|
||||
validator_indices: default_bitvec(n_cores),
|
||||
})
|
||||
.collect();
|
||||
|
||||
test_harness(|r| mock_overseer(r, expected_backed), |mut tx: TestSubsystemSender| async move {
|
||||
let result =
|
||||
select_candidates(&mock_cores, &[], &candidates, Default::default(), &mut tx)
|
||||
.await.unwrap();
|
||||
test_harness(
|
||||
|r| mock_overseer(r, expected_backed),
|
||||
|mut tx: TestSubsystemSender| async move {
|
||||
let result =
|
||||
select_candidates(&mock_cores, &[], &candidates, Default::default(), &mut tx)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
result.into_iter()
|
||||
.for_each(|c|
|
||||
result.into_iter().for_each(|c| {
|
||||
assert!(
|
||||
expected_candidates.iter().any(|c2| c.candidate.corresponds_to(c2)),
|
||||
"Failed to find candidate: {:?}",
|
||||
c,
|
||||
)
|
||||
);
|
||||
})
|
||||
});
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -430,7 +435,11 @@ mod select_candidates {
|
||||
..Default::default()
|
||||
},
|
||||
commitments: CandidateCommitments {
|
||||
new_validation_code: if cores_with_code.contains(&i) { Some(vec![].into()) } else { None },
|
||||
new_validation_code: if cores_with_code.contains(&i) {
|
||||
Some(vec![].into())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
@@ -439,10 +448,8 @@ mod select_candidates {
|
||||
|
||||
let candidates: Vec<_> = committed_receipts.iter().map(|r| r.to_plain()).collect();
|
||||
|
||||
let expected_candidates: Vec<_> = cores
|
||||
.iter()
|
||||
.map(|&idx| candidates[idx].clone())
|
||||
.collect();
|
||||
let expected_candidates: Vec<_> =
|
||||
cores.iter().map(|&idx| candidates[idx].clone()).collect();
|
||||
|
||||
let expected_backed: Vec<_> = cores
|
||||
.iter()
|
||||
@@ -453,19 +460,22 @@ mod select_candidates {
|
||||
})
|
||||
.collect();
|
||||
|
||||
test_harness(|r| mock_overseer(r, expected_backed), |mut tx: TestSubsystemSender| async move {
|
||||
let result =
|
||||
select_candidates(&mock_cores, &[], &candidates, Default::default(), &mut tx)
|
||||
.await.unwrap();
|
||||
test_harness(
|
||||
|r| mock_overseer(r, expected_backed),
|
||||
|mut tx: TestSubsystemSender| async move {
|
||||
let result =
|
||||
select_candidates(&mock_cores, &[], &candidates, Default::default(), &mut tx)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
result.into_iter()
|
||||
.for_each(|c|
|
||||
result.into_iter().for_each(|c| {
|
||||
assert!(
|
||||
expected_candidates.iter().any(|c2| c.candidate.corresponds_to(c2)),
|
||||
"Failed to find candidate: {:?}",
|
||||
c,
|
||||
)
|
||||
);
|
||||
})
|
||||
});
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,15 +15,13 @@
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use always_assert::always;
|
||||
use async_std::{
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use async_std::path::{Path, PathBuf};
|
||||
use parity_scale_codec::{Decode, Encode};
|
||||
use polkadot_parachain::primitives::ValidationCodeHash;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
use parity_scale_codec::{Encode, Decode};
|
||||
|
||||
/// A final product of preparation process. Contains either a ready to run compiled artifact or
|
||||
/// a description what went wrong.
|
||||
@@ -70,8 +68,8 @@ impl ArtifactId {
|
||||
/// Tries to recover the artifact id from the given file name.
|
||||
#[cfg(test)]
|
||||
pub fn from_file_name(file_name: &str) -> Option<Self> {
|
||||
use std::str::FromStr as _;
|
||||
use polkadot_core_primitives::Hash;
|
||||
use std::str::FromStr as _;
|
||||
|
||||
let file_name = file_name.strip_prefix(Self::PREFIX)?;
|
||||
let code_hash = Hash::from_str(file_name).ok()?.into();
|
||||
@@ -123,9 +121,7 @@ impl Artifacts {
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn empty() -> Self {
|
||||
Self {
|
||||
artifacts: HashMap::new(),
|
||||
}
|
||||
Self { artifacts: HashMap::new() }
|
||||
}
|
||||
|
||||
/// Returns the state of the given artifact by its ID.
|
||||
@@ -139,10 +135,7 @@ impl Artifacts {
|
||||
/// replacing existing ones.
|
||||
pub fn insert_preparing(&mut self, artifact_id: ArtifactId) {
|
||||
// See the precondition.
|
||||
always!(self
|
||||
.artifacts
|
||||
.insert(artifact_id, ArtifactState::Preparing)
|
||||
.is_none());
|
||||
always!(self.artifacts.insert(artifact_id, ArtifactState::Preparing).is_none());
|
||||
}
|
||||
|
||||
/// Insert an artifact with the given ID as "prepared".
|
||||
@@ -164,9 +157,7 @@ impl Artifacts {
|
||||
|
||||
let mut to_remove = vec![];
|
||||
for (k, v) in self.artifacts.iter() {
|
||||
if let ArtifactState::Prepared {
|
||||
last_time_needed, ..
|
||||
} = *v {
|
||||
if let ArtifactState::Prepared { last_time_needed, .. } = *v {
|
||||
if now
|
||||
.duration_since(last_time_needed)
|
||||
.map(|age| age > artifact_ttl)
|
||||
@@ -187,8 +178,8 @@ impl Artifacts {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ArtifactId, Artifacts};
|
||||
use async_std::path::Path;
|
||||
use super::{Artifacts, ArtifactId};
|
||||
use sp_core::H256;
|
||||
use std::str::FromStr;
|
||||
|
||||
@@ -213,17 +204,24 @@ mod tests {
|
||||
#[test]
|
||||
fn path() {
|
||||
let path = Path::new("/test");
|
||||
let hash = H256::from_str("1234567890123456789012345678901234567890123456789012345678901234").unwrap().into();
|
||||
let hash =
|
||||
H256::from_str("1234567890123456789012345678901234567890123456789012345678901234")
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
assert_eq!(
|
||||
ArtifactId::new(hash).path(path).to_str(),
|
||||
Some("/test/wasmtime_0x1234567890123456789012345678901234567890123456789012345678901234"),
|
||||
Some(
|
||||
"/test/wasmtime_0x1234567890123456789012345678901234567890123456789012345678901234"
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn artifacts_removes_cache_on_startup() {
|
||||
let fake_cache_path = async_std::task::block_on(async move { crate::worker_common::tmpfile("test-cache").await.unwrap() });
|
||||
let fake_cache_path = async_std::task::block_on(async move {
|
||||
crate::worker_common::tmpfile("test-cache").await.unwrap()
|
||||
});
|
||||
let fake_artifact_path = {
|
||||
let mut p = fake_cache_path.clone();
|
||||
p.push("wasmtime_0x1234567890123456789012345678901234567890123456789012345678901234");
|
||||
|
||||
@@ -23,5 +23,5 @@
|
||||
mod queue;
|
||||
mod worker;
|
||||
|
||||
pub use queue::{ToQueue, start};
|
||||
pub use queue::{start, ToQueue};
|
||||
pub use worker::worker_entrypoint;
|
||||
|
||||
@@ -16,31 +16,27 @@
|
||||
|
||||
//! A queue that handles requests for PVF execution.
|
||||
|
||||
use crate::{
|
||||
worker_common::{IdleWorker, WorkerHandle},
|
||||
host::ResultSender,
|
||||
LOG_TARGET, InvalidCandidate, ValidationError,
|
||||
};
|
||||
use super::worker::Outcome;
|
||||
use std::{collections::VecDeque, fmt, time::Duration};
|
||||
use crate::{
|
||||
host::ResultSender,
|
||||
worker_common::{IdleWorker, WorkerHandle},
|
||||
InvalidCandidate, ValidationError, LOG_TARGET,
|
||||
};
|
||||
use async_std::path::PathBuf;
|
||||
use futures::{
|
||||
Future, FutureExt,
|
||||
channel::mpsc,
|
||||
future::BoxFuture,
|
||||
stream::{FuturesUnordered, StreamExt as _},
|
||||
Future, FutureExt,
|
||||
};
|
||||
use async_std::path::PathBuf;
|
||||
use slotmap::HopSlotMap;
|
||||
use std::{collections::VecDeque, fmt, time::Duration};
|
||||
|
||||
slotmap::new_key_type! { struct Worker; }
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ToQueue {
|
||||
Enqueue {
|
||||
artifact_path: PathBuf,
|
||||
params: Vec<u8>,
|
||||
result_tx: ResultSender,
|
||||
},
|
||||
Enqueue { artifact_path: PathBuf, params: Vec<u8>, result_tx: ResultSender },
|
||||
}
|
||||
|
||||
struct ExecuteJob {
|
||||
@@ -86,11 +82,7 @@ impl Workers {
|
||||
///
|
||||
/// Returns `None` if either worker is not recognized or idle token is absent.
|
||||
fn claim_idle(&mut self, worker: Worker) -> Option<IdleWorker> {
|
||||
self
|
||||
.running
|
||||
.get_mut(worker)?
|
||||
.idle
|
||||
.take()
|
||||
self.running.get_mut(worker)?.idle.take()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,17 +159,9 @@ async fn purge_dead(workers: &mut Workers) {
|
||||
}
|
||||
|
||||
fn handle_to_queue(queue: &mut Queue, to_queue: ToQueue) {
|
||||
let ToQueue::Enqueue {
|
||||
artifact_path,
|
||||
params,
|
||||
result_tx,
|
||||
} = to_queue;
|
||||
let ToQueue::Enqueue { artifact_path, params, result_tx } = to_queue;
|
||||
|
||||
let job = ExecuteJob {
|
||||
artifact_path,
|
||||
params,
|
||||
result_tx,
|
||||
};
|
||||
let job = ExecuteJob { artifact_path, params, result_tx };
|
||||
|
||||
if let Some(available) = queue.workers.find_available() {
|
||||
assign(queue, available, job);
|
||||
@@ -194,18 +178,15 @@ async fn handle_mux(queue: &mut Queue, event: QueueEvent) {
|
||||
QueueEvent::Spawn((idle, handle)) => {
|
||||
queue.workers.spawn_inflight -= 1;
|
||||
|
||||
let worker = queue.workers.running.insert(WorkerData {
|
||||
idle: Some(idle),
|
||||
handle,
|
||||
});
|
||||
let worker = queue.workers.running.insert(WorkerData { idle: Some(idle), handle });
|
||||
|
||||
if let Some(job) = queue.queue.pop_front() {
|
||||
assign(queue, worker, job);
|
||||
}
|
||||
}
|
||||
},
|
||||
QueueEvent::StartWork(worker, outcome, result_tx) => {
|
||||
handle_job_finish(queue, worker, outcome, result_tx);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,38 +194,22 @@ async fn handle_mux(queue: &mut Queue, event: QueueEvent) {
|
||||
/// worker. Otherwise, puts back into the available workers list.
|
||||
fn handle_job_finish(queue: &mut Queue, worker: Worker, outcome: Outcome, result_tx: ResultSender) {
|
||||
let (idle_worker, result) = match outcome {
|
||||
Outcome::Ok {
|
||||
result_descriptor,
|
||||
duration_ms,
|
||||
idle_worker,
|
||||
} => {
|
||||
Outcome::Ok { result_descriptor, duration_ms, idle_worker } => {
|
||||
// TODO: propagate the soft timeout
|
||||
drop(duration_ms);
|
||||
|
||||
(Some(idle_worker), Ok(result_descriptor))
|
||||
}
|
||||
},
|
||||
Outcome::InvalidCandidate { err, idle_worker } => (
|
||||
Some(idle_worker),
|
||||
Err(ValidationError::InvalidCandidate(
|
||||
InvalidCandidate::WorkerReportedError(err),
|
||||
)),
|
||||
),
|
||||
Outcome::InternalError { err, idle_worker } => (
|
||||
Some(idle_worker),
|
||||
Err(ValidationError::InternalError(err)),
|
||||
),
|
||||
Outcome::HardTimeout => (
|
||||
None,
|
||||
Err(ValidationError::InvalidCandidate(
|
||||
InvalidCandidate::HardTimeout,
|
||||
)),
|
||||
),
|
||||
Outcome::IoErr => (
|
||||
None,
|
||||
Err(ValidationError::InvalidCandidate(
|
||||
InvalidCandidate::AmbigiousWorkerDeath,
|
||||
)),
|
||||
Err(ValidationError::InvalidCandidate(InvalidCandidate::WorkerReportedError(err))),
|
||||
),
|
||||
Outcome::InternalError { err, idle_worker } =>
|
||||
(Some(idle_worker), Err(ValidationError::InternalError(err))),
|
||||
Outcome::HardTimeout =>
|
||||
(None, Err(ValidationError::InvalidCandidate(InvalidCandidate::HardTimeout))),
|
||||
Outcome::IoErr =>
|
||||
(None, Err(ValidationError::InvalidCandidate(InvalidCandidate::AmbigiousWorkerDeath))),
|
||||
};
|
||||
|
||||
// First we send the result. It may fail due the other end of the channel being dropped, that's
|
||||
@@ -293,15 +258,11 @@ async fn spawn_worker_task(program_path: PathBuf, spawn_timeout: Duration) -> Qu
|
||||
match super::worker::spawn(&program_path, spawn_timeout).await {
|
||||
Ok((idle, handle)) => break QueueEvent::Spawn((idle, handle)),
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
"failed to spawn an execute worker: {:?}",
|
||||
err,
|
||||
);
|
||||
tracing::warn!(target: LOG_TARGET, "failed to spawn an execute worker: {:?}", err,);
|
||||
|
||||
// Assume that the failure intermittent and retry after a delay.
|
||||
Delay::new(Duration::from_secs(3)).await;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -310,14 +271,11 @@ async fn spawn_worker_task(program_path: PathBuf, spawn_timeout: Duration) -> Qu
|
||||
///
|
||||
/// The worker must be running and idle.
|
||||
fn assign(queue: &mut Queue, worker: Worker, job: ExecuteJob) {
|
||||
let idle = queue
|
||||
.workers
|
||||
.claim_idle(worker)
|
||||
.expect(
|
||||
"this caller must supply a worker which is idle and running;
|
||||
let idle = queue.workers.claim_idle(worker).expect(
|
||||
"this caller must supply a worker which is idle and running;
|
||||
thus claim_idle cannot return None;
|
||||
qed."
|
||||
);
|
||||
qed.",
|
||||
);
|
||||
queue.mux.push(
|
||||
async move {
|
||||
let outcome = super::worker::start_work(idle, job.artifact_path, job.params).await;
|
||||
@@ -333,12 +291,6 @@ pub fn start(
|
||||
spawn_timeout: Duration,
|
||||
) -> (mpsc::Sender<ToQueue>, impl Future<Output = ()>) {
|
||||
let (to_queue_tx, to_queue_rx) = mpsc::channel(20);
|
||||
let run = Queue::new(
|
||||
program_path,
|
||||
worker_capacity,
|
||||
spawn_timeout,
|
||||
to_queue_rx,
|
||||
)
|
||||
.run();
|
||||
let run = Queue::new(program_path, worker_capacity, spawn_timeout, to_queue_rx).run();
|
||||
(to_queue_tx, run)
|
||||
}
|
||||
|
||||
@@ -16,14 +16,13 @@
|
||||
|
||||
use crate::{
|
||||
artifacts::Artifact,
|
||||
LOG_TARGET,
|
||||
executor_intf::TaskExecutor,
|
||||
worker_common::{
|
||||
IdleWorker, SpawnErr, WorkerHandle, bytes_to_path, framed_recv, framed_send, path_to_bytes,
|
||||
spawn_with_program_path, worker_event_loop,
|
||||
bytes_to_path, framed_recv, framed_send, path_to_bytes, spawn_with_program_path,
|
||||
worker_event_loop, IdleWorker, SpawnErr, WorkerHandle,
|
||||
},
|
||||
LOG_TARGET,
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
use async_std::{
|
||||
io,
|
||||
os::unix::net::UnixStream,
|
||||
@@ -31,8 +30,9 @@ use async_std::{
|
||||
};
|
||||
use futures::FutureExt;
|
||||
use futures_timer::Delay;
|
||||
use parity_scale_codec::{Decode, Encode};
|
||||
use polkadot_parachain::primitives::ValidationResult;
|
||||
use parity_scale_codec::{Encode, Decode};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
const EXECUTION_TIMEOUT: Duration = Duration::from_secs(3);
|
||||
|
||||
@@ -43,36 +43,20 @@ pub async fn spawn(
|
||||
program_path: &Path,
|
||||
spawn_timeout: Duration,
|
||||
) -> Result<(IdleWorker, WorkerHandle), SpawnErr> {
|
||||
spawn_with_program_path(
|
||||
"execute",
|
||||
program_path,
|
||||
&["execute-worker"],
|
||||
spawn_timeout,
|
||||
)
|
||||
.await
|
||||
spawn_with_program_path("execute", program_path, &["execute-worker"], spawn_timeout).await
|
||||
}
|
||||
|
||||
/// Outcome of PVF execution.
|
||||
pub enum Outcome {
|
||||
/// PVF execution completed successfully and the result is returned. The worker is ready for
|
||||
/// another job.
|
||||
Ok {
|
||||
result_descriptor: ValidationResult,
|
||||
duration_ms: u64,
|
||||
idle_worker: IdleWorker,
|
||||
},
|
||||
Ok { result_descriptor: ValidationResult, duration_ms: u64, idle_worker: IdleWorker },
|
||||
/// The candidate validation failed. It may be for example because the preparation process
|
||||
/// produced an error or the wasm execution triggered a trap.
|
||||
InvalidCandidate {
|
||||
err: String,
|
||||
idle_worker: IdleWorker,
|
||||
},
|
||||
InvalidCandidate { err: String, idle_worker: IdleWorker },
|
||||
/// An internal error happened during the validation. Such an error is most likely related to
|
||||
/// some transient glitch.
|
||||
InternalError {
|
||||
err: String,
|
||||
idle_worker: IdleWorker,
|
||||
},
|
||||
InternalError { err: String, idle_worker: IdleWorker },
|
||||
/// The execution time exceeded the hard limit. The worker is terminated.
|
||||
HardTimeout,
|
||||
/// An I/O error happened during communication with the worker. This may mean that the worker
|
||||
@@ -97,7 +81,7 @@ pub async fn start_work(
|
||||
);
|
||||
|
||||
if send_request(&mut stream, &artifact_path, &validation_params).await.is_err() {
|
||||
return Outcome::IoErr;
|
||||
return Outcome::IoErr
|
||||
}
|
||||
|
||||
let response = futures::select! {
|
||||
@@ -111,22 +95,12 @@ pub async fn start_work(
|
||||
};
|
||||
|
||||
match response {
|
||||
Response::Ok {
|
||||
result_descriptor,
|
||||
duration_ms,
|
||||
} => Outcome::Ok {
|
||||
result_descriptor,
|
||||
duration_ms,
|
||||
idle_worker: IdleWorker { stream, pid },
|
||||
},
|
||||
Response::InvalidCandidate(err) => Outcome::InvalidCandidate {
|
||||
err,
|
||||
idle_worker: IdleWorker { stream, pid },
|
||||
},
|
||||
Response::InternalError(err) => Outcome::InternalError {
|
||||
err,
|
||||
idle_worker: IdleWorker { stream, pid },
|
||||
},
|
||||
Response::Ok { result_descriptor, duration_ms } =>
|
||||
Outcome::Ok { result_descriptor, duration_ms, idle_worker: IdleWorker { stream, pid } },
|
||||
Response::InvalidCandidate(err) =>
|
||||
Outcome::InvalidCandidate { err, idle_worker: IdleWorker { stream, pid } },
|
||||
Response::InternalError(err) =>
|
||||
Outcome::InternalError { err, idle_worker: IdleWorker { stream, pid } },
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,10 +141,7 @@ async fn recv_response(stream: &mut UnixStream) -> io::Result<Response> {
|
||||
|
||||
#[derive(Encode, Decode)]
|
||||
enum Response {
|
||||
Ok {
|
||||
result_descriptor: ValidationResult,
|
||||
duration_ms: u64,
|
||||
},
|
||||
Ok { result_descriptor: ValidationResult, duration_ms: u64 },
|
||||
InvalidCandidate(String),
|
||||
InternalError(String),
|
||||
}
|
||||
@@ -190,10 +161,7 @@ impl Response {
|
||||
pub fn worker_entrypoint(socket_path: &str) {
|
||||
worker_event_loop("execute", socket_path, |mut stream| async move {
|
||||
let executor = TaskExecutor::new().map_err(|e| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("cannot create task executor: {}", e),
|
||||
)
|
||||
io::Error::new(io::ErrorKind::Other, format!("cannot create task executor: {}", e))
|
||||
})?;
|
||||
loop {
|
||||
let (artifact_path, params) = recv_request(&mut stream).await?;
|
||||
@@ -215,13 +183,12 @@ async fn validate_using_artifact(
|
||||
spawner: &TaskExecutor,
|
||||
) -> Response {
|
||||
let artifact_bytes = match async_std::fs::read(artifact_path).await {
|
||||
Err(e) => {
|
||||
Err(e) =>
|
||||
return Response::InternalError(format!(
|
||||
"failed to read the artifact at {}: {:?}",
|
||||
artifact_path.display(),
|
||||
e,
|
||||
))
|
||||
}
|
||||
)),
|
||||
Ok(b) => b,
|
||||
};
|
||||
|
||||
@@ -231,47 +198,31 @@ async fn validate_using_artifact(
|
||||
};
|
||||
|
||||
let compiled_artifact = match &artifact {
|
||||
Artifact::PrevalidationErr(msg) => {
|
||||
return Response::format_invalid("prevalidation", msg);
|
||||
}
|
||||
Artifact::PreparationErr(msg) => {
|
||||
return Response::format_invalid("preparation", msg);
|
||||
}
|
||||
Artifact::DidntMakeIt => {
|
||||
return Response::format_invalid("preparation timeout", "");
|
||||
}
|
||||
Artifact::PrevalidationErr(msg) => return Response::format_invalid("prevalidation", msg),
|
||||
Artifact::PreparationErr(msg) => return Response::format_invalid("preparation", msg),
|
||||
Artifact::DidntMakeIt => return Response::format_invalid("preparation timeout", ""),
|
||||
|
||||
Artifact::Compiled { compiled_artifact } => compiled_artifact,
|
||||
};
|
||||
|
||||
let validation_started_at = Instant::now();
|
||||
let descriptor_bytes =
|
||||
match unsafe {
|
||||
// SAFETY: this should be safe since the compiled artifact passed here comes from the
|
||||
// file created by the prepare workers. These files are obtained by calling
|
||||
// [`executor_intf::prepare`].
|
||||
crate::executor_intf::execute(compiled_artifact, params, spawner.clone())
|
||||
} {
|
||||
Err(err) => {
|
||||
return Response::format_invalid("execute", &err.to_string());
|
||||
}
|
||||
Ok(d) => d,
|
||||
};
|
||||
let descriptor_bytes = match unsafe {
|
||||
// SAFETY: this should be safe since the compiled artifact passed here comes from the
|
||||
// file created by the prepare workers. These files are obtained by calling
|
||||
// [`executor_intf::prepare`].
|
||||
crate::executor_intf::execute(compiled_artifact, params, spawner.clone())
|
||||
} {
|
||||
Err(err) => return Response::format_invalid("execute", &err.to_string()),
|
||||
Ok(d) => d,
|
||||
};
|
||||
|
||||
let duration_ms = validation_started_at.elapsed().as_millis() as u64;
|
||||
|
||||
let result_descriptor = match ValidationResult::decode(&mut &descriptor_bytes[..]) {
|
||||
Err(err) => {
|
||||
return Response::InvalidCandidate(format!(
|
||||
"validation result decoding failed: {}",
|
||||
err
|
||||
))
|
||||
}
|
||||
Err(err) =>
|
||||
return Response::InvalidCandidate(format!("validation result decoding failed: {}", err)),
|
||||
Ok(r) => r,
|
||||
};
|
||||
|
||||
Response::Ok {
|
||||
result_descriptor,
|
||||
duration_ms,
|
||||
}
|
||||
Response::Ok { result_descriptor, duration_ms }
|
||||
}
|
||||
|
||||
@@ -16,16 +16,14 @@
|
||||
|
||||
//! Interface to the Substrate Executor
|
||||
|
||||
use std::any::{TypeId, Any};
|
||||
use sc_executor_common::{
|
||||
runtime_blob::RuntimeBlob,
|
||||
wasm_runtime::{InvokeMethod, WasmModule as _},
|
||||
};
|
||||
use sc_executor_wasmtime::{Config, Semantics, DeterministicStackLimit};
|
||||
use sp_core::{
|
||||
storage::{ChildInfo, TrackedStorageKey},
|
||||
};
|
||||
use sc_executor_wasmtime::{Config, DeterministicStackLimit, Semantics};
|
||||
use sp_core::storage::{ChildInfo, TrackedStorageKey};
|
||||
use sp_wasm_interface::HostFunctions as _;
|
||||
use std::any::{Any, TypeId};
|
||||
|
||||
const CONFIG: Config = Config {
|
||||
// TODO: Make sure we don't use more than 1GB: https://github.com/paritytech/polkadot/issues/699
|
||||
@@ -95,9 +93,7 @@ pub unsafe fn execute(
|
||||
CONFIG,
|
||||
HostFunctions::host_functions(),
|
||||
)?;
|
||||
runtime
|
||||
.new_instance()?
|
||||
.call(InvokeMethod::Export("validate_block"), params)
|
||||
runtime.new_instance()?.call(InvokeMethod::Export("validate_block"), params)
|
||||
})?
|
||||
}
|
||||
|
||||
|
||||
@@ -21,23 +21,20 @@
|
||||
//! [`ValidationHost`], that allows communication with that event-loop.
|
||||
|
||||
use crate::{
|
||||
Priority, Pvf, ValidationError,
|
||||
artifacts::{Artifacts, ArtifactState, ArtifactId},
|
||||
execute, prepare,
|
||||
artifacts::{ArtifactId, ArtifactState, Artifacts},
|
||||
execute, prepare, Priority, Pvf, ValidationError,
|
||||
};
|
||||
use always_assert::never;
|
||||
use async_std::path::{Path, PathBuf};
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
Future, FutureExt, SinkExt, StreamExt,
|
||||
};
|
||||
use polkadot_parachain::primitives::ValidationResult;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
use always_assert::never;
|
||||
use async_std::{
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use polkadot_parachain::primitives::ValidationResult;
|
||||
use futures::{
|
||||
Future, FutureExt, SinkExt, StreamExt,
|
||||
channel::{mpsc, oneshot},
|
||||
};
|
||||
|
||||
/// An alias to not spell the type for the oneshot sender for the PVF execution result.
|
||||
pub(crate) type ResultSender = oneshot::Sender<Result<ValidationResult, ValidationError>>;
|
||||
@@ -64,12 +61,7 @@ impl ValidationHost {
|
||||
result_tx: ResultSender,
|
||||
) -> Result<(), String> {
|
||||
self.to_host_tx
|
||||
.send(ToHost::ExecutePvf {
|
||||
pvf,
|
||||
params,
|
||||
priority,
|
||||
result_tx,
|
||||
})
|
||||
.send(ToHost::ExecutePvf { pvf, params, priority, result_tx })
|
||||
.await
|
||||
.map_err(|_| "the inner loop hung up".to_string())
|
||||
}
|
||||
@@ -89,15 +81,8 @@ impl ValidationHost {
|
||||
}
|
||||
|
||||
enum ToHost {
|
||||
ExecutePvf {
|
||||
pvf: Pvf,
|
||||
params: Vec<u8>,
|
||||
priority: Priority,
|
||||
result_tx: ResultSender,
|
||||
},
|
||||
HeadsUp {
|
||||
active_pvfs: Vec<Pvf>,
|
||||
},
|
||||
ExecutePvf { pvf: Pvf, params: Vec<u8>, priority: Priority, result_tx: ResultSender },
|
||||
HeadsUp { active_pvfs: Vec<Pvf> },
|
||||
}
|
||||
|
||||
/// Configuration for the validation host.
|
||||
@@ -180,12 +165,7 @@ pub fn start(config: Config) -> (ValidationHost, impl Future<Output = ()>) {
|
||||
let run = async move {
|
||||
let artifacts = Artifacts::new(&config.cache_path).await;
|
||||
|
||||
futures::pin_mut!(
|
||||
run_prepare_queue,
|
||||
run_prepare_pool,
|
||||
run_execute_queue,
|
||||
run_sweeper
|
||||
);
|
||||
futures::pin_mut!(run_prepare_queue, run_prepare_pool, run_execute_queue, run_sweeper);
|
||||
|
||||
run(
|
||||
Inner {
|
||||
@@ -375,12 +355,7 @@ async fn handle_to_host(
|
||||
to_host: ToHost,
|
||||
) -> Result<(), Fatal> {
|
||||
match to_host {
|
||||
ToHost::ExecutePvf {
|
||||
pvf,
|
||||
params,
|
||||
priority,
|
||||
result_tx,
|
||||
} => {
|
||||
ToHost::ExecutePvf { pvf, params, priority, result_tx } => {
|
||||
handle_execute_pvf(
|
||||
cache_path,
|
||||
artifacts,
|
||||
@@ -393,10 +368,10 @@ async fn handle_to_host(
|
||||
result_tx,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
},
|
||||
ToHost::HeadsUp { active_pvfs } => {
|
||||
handle_heads_up(artifacts, prepare_queue, active_pvfs).await?;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -417,9 +392,7 @@ async fn handle_execute_pvf(
|
||||
|
||||
if let Some(state) = artifacts.artifact_state_mut(&artifact_id) {
|
||||
match state {
|
||||
ArtifactState::Prepared {
|
||||
ref mut last_time_needed,
|
||||
} => {
|
||||
ArtifactState::Prepared { ref mut last_time_needed } => {
|
||||
*last_time_needed = SystemTime::now();
|
||||
|
||||
send_execute(
|
||||
@@ -431,19 +404,16 @@ async fn handle_execute_pvf(
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
},
|
||||
ArtifactState::Preparing => {
|
||||
send_prepare(
|
||||
prepare_queue,
|
||||
prepare::ToQueue::Amend {
|
||||
priority,
|
||||
artifact_id: artifact_id.clone(),
|
||||
},
|
||||
prepare::ToQueue::Amend { priority, artifact_id: artifact_id.clone() },
|
||||
)
|
||||
.await?;
|
||||
|
||||
awaiting_prepare.add(artifact_id, params, result_tx);
|
||||
}
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// Artifact is unknown: register it and enqueue a job with the corresponding priority and
|
||||
@@ -454,7 +424,7 @@ async fn handle_execute_pvf(
|
||||
awaiting_prepare.add(artifact_id, params, result_tx);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
async fn handle_heads_up(
|
||||
@@ -468,15 +438,13 @@ async fn handle_heads_up(
|
||||
let artifact_id = active_pvf.as_artifact_id();
|
||||
if let Some(state) = artifacts.artifact_state_mut(&artifact_id) {
|
||||
match state {
|
||||
ArtifactState::Prepared {
|
||||
last_time_needed, ..
|
||||
} => {
|
||||
ArtifactState::Prepared { last_time_needed, .. } => {
|
||||
*last_time_needed = now;
|
||||
}
|
||||
},
|
||||
ArtifactState::Preparing => {
|
||||
// Already preparing. We don't need to send a priority amend either because
|
||||
// it can't get any lower than the background.
|
||||
}
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// The artifact is unknown: register it and put a background job into the prepare queue.
|
||||
@@ -484,10 +452,7 @@ async fn handle_heads_up(
|
||||
|
||||
send_prepare(
|
||||
prepare_queue,
|
||||
prepare::ToQueue::Enqueue {
|
||||
priority: Priority::Background,
|
||||
pvf: active_pvf,
|
||||
},
|
||||
prepare::ToQueue::Enqueue { priority: Priority::Background, pvf: active_pvf },
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@@ -512,8 +477,8 @@ async fn handle_prepare_done(
|
||||
// thus the artifact cannot be unknown, only preparing;
|
||||
// qed.
|
||||
never!("an unknown artifact was prepared: {:?}", artifact_id);
|
||||
return Ok(());
|
||||
}
|
||||
return Ok(())
|
||||
},
|
||||
Some(ArtifactState::Prepared { .. }) => {
|
||||
// before sending request to prepare, the artifact is inserted with `preparing` state;
|
||||
// the requests are deduplicated for the same artifact id;
|
||||
@@ -521,8 +486,8 @@ async fn handle_prepare_done(
|
||||
// thus the artifact cannot be prepared, only preparing;
|
||||
// qed.
|
||||
never!("the artifact is already prepared: {:?}", artifact_id);
|
||||
return Ok(());
|
||||
}
|
||||
return Ok(())
|
||||
},
|
||||
Some(state @ ArtifactState::Preparing) => state,
|
||||
};
|
||||
|
||||
@@ -534,24 +499,18 @@ async fn handle_prepare_done(
|
||||
if result_tx.is_canceled() {
|
||||
// Preparation could've taken quite a bit of time and the requester may be not interested
|
||||
// in execution anymore, in which case we just skip the request.
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
|
||||
send_execute(
|
||||
execute_queue,
|
||||
execute::ToQueue::Enqueue {
|
||||
artifact_path: artifact_path.clone(),
|
||||
params,
|
||||
result_tx,
|
||||
},
|
||||
execute::ToQueue::Enqueue { artifact_path: artifact_path.clone(), params, result_tx },
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Now consider the artifact prepared.
|
||||
*state = ArtifactState::Prepared {
|
||||
last_time_needed: SystemTime::now(),
|
||||
};
|
||||
*state = ArtifactState::Prepared { last_time_needed: SystemTime::now() };
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -592,7 +551,7 @@ async fn sweeper_task(mut sweeper_rx: mpsc::Receiver<PathBuf>) {
|
||||
None => break,
|
||||
Some(condemned) => {
|
||||
let _ = async_std::fs::remove_file(condemned).await;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -611,8 +570,8 @@ fn pulse_every(interval: std::time::Duration) -> impl futures::Stream<Item = ()>
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use futures::future::BoxFuture;
|
||||
use assert_matches::assert_matches;
|
||||
use futures::future::BoxFuture;
|
||||
|
||||
#[async_std::test]
|
||||
async fn pulse_test() {
|
||||
@@ -634,9 +593,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn artifact_path(descriminator: u32) -> PathBuf {
|
||||
artifact_id(descriminator)
|
||||
.path(&PathBuf::from(std::env::temp_dir()))
|
||||
.to_owned()
|
||||
artifact_id(descriminator).path(&PathBuf::from(std::env::temp_dir())).to_owned()
|
||||
}
|
||||
|
||||
struct Builder {
|
||||
@@ -673,13 +630,7 @@ mod tests {
|
||||
}
|
||||
|
||||
impl Test {
|
||||
fn new(
|
||||
Builder {
|
||||
cleanup_pulse_interval,
|
||||
artifact_ttl,
|
||||
artifacts,
|
||||
}: Builder,
|
||||
) -> Self {
|
||||
fn new(Builder { cleanup_pulse_interval, artifact_ttl, artifacts }: Builder) -> Self {
|
||||
let cache_path = PathBuf::from(std::env::temp_dir());
|
||||
|
||||
let (to_host_tx, to_host_rx) = mpsc::channel(10);
|
||||
@@ -727,20 +678,14 @@ mod tests {
|
||||
|
||||
async fn poll_and_recv_to_prepare_queue(&mut self) -> prepare::ToQueue {
|
||||
let to_prepare_queue_rx = &mut self.to_prepare_queue_rx;
|
||||
run_until(
|
||||
&mut self.run,
|
||||
async { to_prepare_queue_rx.next().await.unwrap() }.boxed(),
|
||||
)
|
||||
.await
|
||||
run_until(&mut self.run, async { to_prepare_queue_rx.next().await.unwrap() }.boxed())
|
||||
.await
|
||||
}
|
||||
|
||||
async fn poll_and_recv_to_execute_queue(&mut self) -> execute::ToQueue {
|
||||
let to_execute_queue_rx = &mut self.to_execute_queue_rx;
|
||||
run_until(
|
||||
&mut self.run,
|
||||
async { to_execute_queue_rx.next().await.unwrap() }.boxed(),
|
||||
)
|
||||
.await
|
||||
run_until(&mut self.run, async { to_execute_queue_rx.next().await.unwrap() }.boxed())
|
||||
.await
|
||||
}
|
||||
|
||||
async fn poll_ensure_to_execute_queue_is_empty(&mut self) {
|
||||
@@ -798,7 +743,7 @@ mod tests {
|
||||
}
|
||||
|
||||
if let Poll::Ready(r) = futures::poll!(&mut *fut) {
|
||||
break r;
|
||||
break r
|
||||
}
|
||||
|
||||
if futures::poll!(&mut *task).is_ready() {
|
||||
@@ -831,9 +776,7 @@ mod tests {
|
||||
let mut test = builder.build();
|
||||
let mut host = test.host_handle();
|
||||
|
||||
host.heads_up(vec![Pvf::from_discriminator(1)])
|
||||
.await
|
||||
.unwrap();
|
||||
host.heads_up(vec![Pvf::from_discriminator(1)]).await.unwrap();
|
||||
|
||||
let to_sweeper_rx = &mut test.to_sweeper_rx;
|
||||
run_until(
|
||||
@@ -847,9 +790,7 @@ mod tests {
|
||||
|
||||
// Extend TTL for the first artifact and make sure we don't receive another file removal
|
||||
// request.
|
||||
host.heads_up(vec![Pvf::from_discriminator(1)])
|
||||
.await
|
||||
.unwrap();
|
||||
host.heads_up(vec![Pvf::from_discriminator(1)]).await.unwrap();
|
||||
test.poll_ensure_to_sweeper_is_empty().await;
|
||||
}
|
||||
|
||||
@@ -858,9 +799,7 @@ mod tests {
|
||||
let mut test = Builder::default().build();
|
||||
let mut host = test.host_handle();
|
||||
|
||||
host.heads_up(vec![Pvf::from_discriminator(1)])
|
||||
.await
|
||||
.unwrap();
|
||||
host.heads_up(vec![Pvf::from_discriminator(1)]).await.unwrap();
|
||||
|
||||
// Run until we receive a prepare request.
|
||||
let prepare_q_rx = &mut test.to_prepare_queue_rx;
|
||||
@@ -877,22 +816,14 @@ mod tests {
|
||||
.await;
|
||||
|
||||
let (result_tx, _result_rx) = oneshot::channel();
|
||||
host.execute_pvf(
|
||||
Pvf::from_discriminator(1),
|
||||
vec![],
|
||||
Priority::Critical,
|
||||
result_tx,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
host.execute_pvf(Pvf::from_discriminator(1), vec![], Priority::Critical, result_tx)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
run_until(
|
||||
&mut test.run,
|
||||
async {
|
||||
assert_matches!(
|
||||
prepare_q_rx.next().await.unwrap(),
|
||||
prepare::ToQueue::Amend { .. }
|
||||
);
|
||||
assert_matches!(prepare_q_rx.next().await.unwrap(), prepare::ToQueue::Amend { .. });
|
||||
}
|
||||
.boxed(),
|
||||
)
|
||||
@@ -907,14 +838,9 @@ mod tests {
|
||||
let mut host = test.host_handle();
|
||||
|
||||
let (result_tx, result_rx_pvf_1_1) = oneshot::channel();
|
||||
host.execute_pvf(
|
||||
Pvf::from_discriminator(1),
|
||||
b"pvf1".to_vec(),
|
||||
Priority::Normal,
|
||||
result_tx,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
host.execute_pvf(Pvf::from_discriminator(1), b"pvf1".to_vec(), Priority::Normal, result_tx)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let (result_tx, result_rx_pvf_1_2) = oneshot::channel();
|
||||
host.execute_pvf(
|
||||
@@ -927,14 +853,9 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
let (result_tx, result_rx_pvf_2) = oneshot::channel();
|
||||
host.execute_pvf(
|
||||
Pvf::from_discriminator(2),
|
||||
b"pvf2".to_vec(),
|
||||
Priority::Normal,
|
||||
result_tx,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
host.execute_pvf(Pvf::from_discriminator(2), b"pvf2".to_vec(), Priority::Normal, result_tx)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_matches!(
|
||||
test.poll_and_recv_to_prepare_queue().await,
|
||||
@@ -972,39 +893,27 @@ mod tests {
|
||||
);
|
||||
|
||||
result_tx_pvf_1_1
|
||||
.send(Err(ValidationError::InvalidCandidate(
|
||||
InvalidCandidate::AmbigiousWorkerDeath,
|
||||
)))
|
||||
.send(Err(ValidationError::InvalidCandidate(InvalidCandidate::AmbigiousWorkerDeath)))
|
||||
.unwrap();
|
||||
assert_matches!(
|
||||
result_rx_pvf_1_1.now_or_never().unwrap().unwrap(),
|
||||
Err(ValidationError::InvalidCandidate(
|
||||
InvalidCandidate::AmbigiousWorkerDeath,
|
||||
))
|
||||
Err(ValidationError::InvalidCandidate(InvalidCandidate::AmbigiousWorkerDeath,))
|
||||
);
|
||||
|
||||
result_tx_pvf_1_2
|
||||
.send(Err(ValidationError::InvalidCandidate(
|
||||
InvalidCandidate::AmbigiousWorkerDeath,
|
||||
)))
|
||||
.send(Err(ValidationError::InvalidCandidate(InvalidCandidate::AmbigiousWorkerDeath)))
|
||||
.unwrap();
|
||||
assert_matches!(
|
||||
result_rx_pvf_1_2.now_or_never().unwrap().unwrap(),
|
||||
Err(ValidationError::InvalidCandidate(
|
||||
InvalidCandidate::AmbigiousWorkerDeath,
|
||||
))
|
||||
Err(ValidationError::InvalidCandidate(InvalidCandidate::AmbigiousWorkerDeath,))
|
||||
);
|
||||
|
||||
result_tx_pvf_2
|
||||
.send(Err(ValidationError::InvalidCandidate(
|
||||
InvalidCandidate::AmbigiousWorkerDeath,
|
||||
)))
|
||||
.send(Err(ValidationError::InvalidCandidate(InvalidCandidate::AmbigiousWorkerDeath)))
|
||||
.unwrap();
|
||||
assert_matches!(
|
||||
result_rx_pvf_2.now_or_never().unwrap().unwrap(),
|
||||
Err(ValidationError::InvalidCandidate(
|
||||
InvalidCandidate::AmbigiousWorkerDeath,
|
||||
))
|
||||
Err(ValidationError::InvalidCandidate(InvalidCandidate::AmbigiousWorkerDeath,))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1014,14 +923,9 @@ mod tests {
|
||||
let mut host = test.host_handle();
|
||||
|
||||
let (result_tx, result_rx) = oneshot::channel();
|
||||
host.execute_pvf(
|
||||
Pvf::from_discriminator(1),
|
||||
b"pvf1".to_vec(),
|
||||
Priority::Normal,
|
||||
result_tx,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
host.execute_pvf(Pvf::from_discriminator(1), b"pvf1".to_vec(), Priority::Normal, result_tx)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_matches!(
|
||||
test.poll_and_recv_to_prepare_queue().await,
|
||||
|
||||
@@ -91,7 +91,7 @@ pub mod testing;
|
||||
#[doc(hidden)]
|
||||
pub use sp_tracing;
|
||||
|
||||
pub use error::{ValidationError, InvalidCandidate};
|
||||
pub use error::{InvalidCandidate, ValidationError};
|
||||
pub use priority::Priority;
|
||||
pub use pvf::Pvf;
|
||||
|
||||
|
||||
@@ -26,6 +26,6 @@ mod pool;
|
||||
mod queue;
|
||||
mod worker;
|
||||
|
||||
pub use queue::{ToQueue, FromQueue, start as start_queue};
|
||||
pub use pool::start as start_pool;
|
||||
pub use queue::{start as start_queue, FromQueue, ToQueue};
|
||||
pub use worker::worker_entrypoint;
|
||||
|
||||
@@ -14,21 +14,19 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::worker::{self, Outcome};
|
||||
use crate::{
|
||||
worker_common::{IdleWorker, WorkerHandle},
|
||||
LOG_TARGET,
|
||||
};
|
||||
use super::{
|
||||
worker::{self, Outcome},
|
||||
};
|
||||
use std::{fmt, sync::Arc, task::Poll, time::Duration};
|
||||
use always_assert::never;
|
||||
use assert_matches::assert_matches;
|
||||
use async_std::path::{Path, PathBuf};
|
||||
use futures::{
|
||||
Future, FutureExt, StreamExt, channel::mpsc, future::BoxFuture, stream::FuturesUnordered,
|
||||
channel::mpsc, future::BoxFuture, stream::FuturesUnordered, Future, FutureExt, StreamExt,
|
||||
};
|
||||
use slotmap::HopSlotMap;
|
||||
use assert_matches::assert_matches;
|
||||
use always_assert::never;
|
||||
use std::{fmt, sync::Arc, task::Poll, time::Duration};
|
||||
|
||||
slotmap::new_key_type! { pub struct Worker; }
|
||||
|
||||
@@ -170,7 +168,7 @@ async fn purge_dead(
|
||||
// The idle token is missing, meaning this worker is now occupied: skip it. This is
|
||||
// because the worker process is observed by the work task and should it reach the
|
||||
// deadline or be terminated it will be handled by the corresponding mux event.
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
|
||||
if let Poll::Ready(()) = futures::poll!(&mut data.handle) {
|
||||
@@ -197,13 +195,8 @@ fn handle_to_pool(
|
||||
match to_pool {
|
||||
ToPool::Spawn => {
|
||||
mux.push(spawn_worker_task(program_path.to_owned(), spawn_timeout).boxed());
|
||||
}
|
||||
ToPool::StartWork {
|
||||
worker,
|
||||
code,
|
||||
artifact_path,
|
||||
background_priority,
|
||||
} => {
|
||||
},
|
||||
ToPool::StartWork { worker, code, artifact_path, background_priority } => {
|
||||
if let Some(data) = spawned.get_mut(worker) {
|
||||
if let Some(idle) = data.idle.take() {
|
||||
mux.push(
|
||||
@@ -213,7 +206,7 @@ fn handle_to_pool(
|
||||
code,
|
||||
cache_path.to_owned(),
|
||||
artifact_path,
|
||||
background_priority
|
||||
background_priority,
|
||||
)
|
||||
.boxed(),
|
||||
);
|
||||
@@ -229,16 +222,15 @@ fn handle_to_pool(
|
||||
// That's a relatively normal situation since the queue may send `start_work` and
|
||||
// before receiving it the pool would report that the worker died.
|
||||
}
|
||||
}
|
||||
},
|
||||
ToPool::Kill(worker) => {
|
||||
// It may be absent if it were previously already removed by `purge_dead`.
|
||||
let _ = spawned.remove(worker);
|
||||
}
|
||||
ToPool::BumpPriority(worker) => {
|
||||
},
|
||||
ToPool::BumpPriority(worker) =>
|
||||
if let Some(data) = spawned.get(worker) {
|
||||
worker::bump_priority(&data.handle);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,15 +241,11 @@ async fn spawn_worker_task(program_path: PathBuf, spawn_timeout: Duration) -> Po
|
||||
match worker::spawn(&program_path, spawn_timeout).await {
|
||||
Ok((idle, handle)) => break PoolEvent::Spawn(idle, handle),
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
"failed to spawn a prepare worker: {:?}",
|
||||
err,
|
||||
);
|
||||
tracing::warn!(target: LOG_TARGET, "failed to spawn a prepare worker: {:?}", err,);
|
||||
|
||||
// Assume that the failure intermittent and retry after a delay.
|
||||
Delay::new(Duration::from_secs(3)).await;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -282,15 +270,12 @@ fn handle_mux(
|
||||
) -> Result<(), Fatal> {
|
||||
match event {
|
||||
PoolEvent::Spawn(idle, handle) => {
|
||||
let worker = spawned.insert(WorkerData {
|
||||
idle: Some(idle),
|
||||
handle,
|
||||
});
|
||||
let worker = spawned.insert(WorkerData { idle: Some(idle), handle });
|
||||
|
||||
reply(from_pool, FromPool::Spawned(worker))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
PoolEvent::StartWork(worker, outcome) => {
|
||||
match outcome {
|
||||
Outcome::Concluded(idle) => {
|
||||
@@ -298,8 +283,8 @@ fn handle_mux(
|
||||
None => {
|
||||
// Perhaps the worker was killed meanwhile and the result is no longer
|
||||
// relevant.
|
||||
return Ok(());
|
||||
}
|
||||
return Ok(())
|
||||
},
|
||||
Some(data) => data,
|
||||
};
|
||||
|
||||
@@ -311,23 +296,23 @@ fn handle_mux(
|
||||
reply(from_pool, FromPool::Concluded(worker, false))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
Outcome::Unreachable => {
|
||||
if spawned.remove(worker).is_some() {
|
||||
reply(from_pool, FromPool::Rip(worker))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
Outcome::DidntMakeIt => {
|
||||
if spawned.remove(worker).is_some() {
|
||||
reply(from_pool, FromPool::Concluded(worker, true))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,11 +325,7 @@ pub fn start(
|
||||
program_path: PathBuf,
|
||||
cache_path: PathBuf,
|
||||
spawn_timeout: Duration,
|
||||
) -> (
|
||||
mpsc::Sender<ToPool>,
|
||||
mpsc::UnboundedReceiver<FromPool>,
|
||||
impl Future<Output = ()>,
|
||||
) {
|
||||
) -> (mpsc::Sender<ToPool>, mpsc::UnboundedReceiver<FromPool>, impl Future<Output = ()>) {
|
||||
let (to_pool_tx, to_pool_rx) = mpsc::channel(10);
|
||||
let (from_pool_tx, from_pool_rx) = mpsc::unbounded();
|
||||
|
||||
|
||||
@@ -16,14 +16,12 @@
|
||||
|
||||
//! A queue that handles requests for PVF preparation.
|
||||
|
||||
use super::{
|
||||
pool::{self, Worker},
|
||||
};
|
||||
use crate::{LOG_TARGET, Priority, Pvf, artifacts::ArtifactId};
|
||||
use futures::{Future, SinkExt, channel::mpsc, stream::StreamExt as _};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use async_std::path::PathBuf;
|
||||
use super::pool::{self, Worker};
|
||||
use crate::{artifacts::ArtifactId, Priority, Pvf, LOG_TARGET};
|
||||
use always_assert::{always, never};
|
||||
use async_std::path::PathBuf;
|
||||
use futures::{channel::mpsc, stream::StreamExt as _, Future, SinkExt};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
/// A request to pool.
|
||||
#[derive(Debug)]
|
||||
@@ -35,10 +33,7 @@ pub enum ToQueue {
|
||||
/// [`ToQueue::Amend`].
|
||||
Enqueue { priority: Priority, pvf: Pvf },
|
||||
/// Amends the priority for the given [`ArtifactId`] if it is running. If it's not, then it's noop.
|
||||
Amend {
|
||||
priority: Priority,
|
||||
artifact_id: ArtifactId,
|
||||
},
|
||||
Amend { priority: Priority, artifact_id: ArtifactId },
|
||||
}
|
||||
|
||||
/// A response from queue.
|
||||
@@ -62,11 +57,7 @@ struct Limits {
|
||||
impl Limits {
|
||||
/// Returns `true` if the queue is allowed to request one more worker.
|
||||
fn can_afford_one_more(&self, spawned_num: usize, critical: bool) -> bool {
|
||||
let cap = if critical {
|
||||
self.hard_capacity
|
||||
} else {
|
||||
self.soft_capacity
|
||||
};
|
||||
let cap = if critical { self.hard_capacity } else { self.soft_capacity };
|
||||
spawned_num < cap
|
||||
}
|
||||
|
||||
@@ -179,10 +170,7 @@ impl Queue {
|
||||
from_pool_rx,
|
||||
cache_path,
|
||||
spawn_inflight: 0,
|
||||
limits: Limits {
|
||||
hard_capacity,
|
||||
soft_capacity,
|
||||
},
|
||||
limits: Limits { hard_capacity, soft_capacity },
|
||||
jobs: slotmap::SlotMap::with_key(),
|
||||
unscheduled: Unscheduled::default(),
|
||||
artifact_id_to_job: HashMap::new(),
|
||||
@@ -194,7 +182,7 @@ impl Queue {
|
||||
macro_rules! break_if_fatal {
|
||||
($expr:expr) => {
|
||||
if let Err(Fatal) = $expr {
|
||||
break;
|
||||
break
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -215,13 +203,10 @@ async fn handle_to_queue(queue: &mut Queue, to_queue: ToQueue) -> Result<(), Fat
|
||||
match to_queue {
|
||||
ToQueue::Enqueue { priority, pvf } => {
|
||||
handle_enqueue(queue, priority, pvf).await?;
|
||||
}
|
||||
ToQueue::Amend {
|
||||
priority,
|
||||
artifact_id,
|
||||
} => {
|
||||
},
|
||||
ToQueue::Amend { priority, artifact_id } => {
|
||||
handle_amend(queue, priority, artifact_id).await?;
|
||||
}
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -241,14 +226,10 @@ async fn handle_enqueue(queue: &mut Queue, priority: Priority, pvf: Pvf) -> Resu
|
||||
"duplicate `enqueue` command received for {:?}",
|
||||
artifact_id,
|
||||
);
|
||||
return Ok(());
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let job = queue.jobs.insert(JobData {
|
||||
priority,
|
||||
pvf,
|
||||
worker: None,
|
||||
});
|
||||
let job = queue.jobs.insert(JobData { priority, pvf, worker: None });
|
||||
queue.artifact_id_to_job.insert(artifact_id, job);
|
||||
|
||||
if let Some(available) = find_idle_worker(queue) {
|
||||
@@ -264,12 +245,7 @@ async fn handle_enqueue(queue: &mut Queue, priority: Priority, pvf: Pvf) -> Resu
|
||||
}
|
||||
|
||||
fn find_idle_worker(queue: &mut Queue) -> Option<Worker> {
|
||||
queue
|
||||
.workers
|
||||
.iter()
|
||||
.filter(|(_, data)| data.is_idle())
|
||||
.map(|(k, _)| k)
|
||||
.next()
|
||||
queue.workers.iter().filter(|(_, data)| data.is_idle()).map(|(k, _)| k).next()
|
||||
}
|
||||
|
||||
async fn handle_amend(
|
||||
@@ -336,8 +312,8 @@ async fn handle_worker_concluded(
|
||||
// Assume the conditions holds, then this never is not hit;
|
||||
// qed.
|
||||
never!("never_none, {}", stringify!($expr));
|
||||
return Ok(());
|
||||
}
|
||||
return Ok(())
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -388,10 +364,7 @@ async fn handle_worker_concluded(
|
||||
spawn_extra_worker(queue, false).await?;
|
||||
}
|
||||
} else {
|
||||
if queue
|
||||
.limits
|
||||
.should_cull(queue.workers.len() + queue.spawn_inflight)
|
||||
{
|
||||
if queue.limits.should_cull(queue.workers.len() + queue.spawn_inflight) {
|
||||
// We no longer need services of this worker. Kill it.
|
||||
queue.workers.remove(worker);
|
||||
send_pool(&mut queue.to_pool_tx, pool::ToPool::Kill(worker)).await?;
|
||||
@@ -412,20 +385,16 @@ async fn handle_worker_rip(queue: &mut Queue, worker: Worker) -> Result<(), Fata
|
||||
if let Some(WorkerData { job: Some(job), .. }) = worker_data {
|
||||
// This is an edge case where the worker ripped after we sent assignment but before it
|
||||
// was received by the pool.
|
||||
let priority = queue
|
||||
.jobs
|
||||
.get(job)
|
||||
.map(|data| data.priority)
|
||||
.unwrap_or_else(|| {
|
||||
// job is inserted upon enqueue and removed on concluded signal;
|
||||
// this is enclosed in the if statement that narrows the situation to before
|
||||
// conclusion;
|
||||
// that means that the job still exists and is known;
|
||||
// this path cannot be hit;
|
||||
// qed.
|
||||
never!("the job of the ripped worker must be known but it is not");
|
||||
Priority::Normal
|
||||
});
|
||||
let priority = queue.jobs.get(job).map(|data| data.priority).unwrap_or_else(|| {
|
||||
// job is inserted upon enqueue and removed on concluded signal;
|
||||
// this is enclosed in the if statement that narrows the situation to before
|
||||
// conclusion;
|
||||
// that means that the job still exists and is known;
|
||||
// this path cannot be hit;
|
||||
// qed.
|
||||
never!("the job of the ripped worker must be known but it is not");
|
||||
Priority::Normal
|
||||
});
|
||||
queue.unscheduled.readd(priority, job);
|
||||
}
|
||||
|
||||
@@ -500,11 +469,7 @@ pub fn start(
|
||||
cache_path: PathBuf,
|
||||
to_pool_tx: mpsc::Sender<pool::ToPool>,
|
||||
from_pool_rx: mpsc::UnboundedReceiver<pool::FromPool>,
|
||||
) -> (
|
||||
mpsc::Sender<ToQueue>,
|
||||
mpsc::UnboundedReceiver<FromQueue>,
|
||||
impl Future<Output = ()>,
|
||||
) {
|
||||
) -> (mpsc::Sender<ToQueue>, mpsc::UnboundedReceiver<FromQueue>, impl Future<Output = ()>) {
|
||||
let (to_queue_tx, to_queue_rx) = mpsc::channel(150);
|
||||
let (from_queue_tx, from_queue_rx) = mpsc::unbounded();
|
||||
|
||||
@@ -524,11 +489,11 @@ pub fn start(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use slotmap::SlotMap;
|
||||
use assert_matches::assert_matches;
|
||||
use futures::{FutureExt, future::BoxFuture};
|
||||
use std::task::Poll;
|
||||
use super::*;
|
||||
use assert_matches::assert_matches;
|
||||
use futures::{future::BoxFuture, FutureExt};
|
||||
use slotmap::SlotMap;
|
||||
use std::task::Poll;
|
||||
|
||||
/// Creates a new PVF which artifact id can be uniquely identified by the given number.
|
||||
fn pvf(descriminator: u32) -> Pvf {
|
||||
@@ -549,7 +514,7 @@ mod tests {
|
||||
}
|
||||
|
||||
if let Poll::Ready(r) = futures::poll!(&mut *fut) {
|
||||
break r;
|
||||
break r
|
||||
}
|
||||
|
||||
if futures::poll!(&mut *task).is_ready() {
|
||||
@@ -597,37 +562,21 @@ mod tests {
|
||||
}
|
||||
|
||||
fn send_queue(&mut self, to_queue: ToQueue) {
|
||||
self.to_queue_tx
|
||||
.send(to_queue)
|
||||
.now_or_never()
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
self.to_queue_tx.send(to_queue).now_or_never().unwrap().unwrap();
|
||||
}
|
||||
|
||||
async fn poll_and_recv_from_queue(&mut self) -> FromQueue {
|
||||
let from_queue_rx = &mut self.from_queue_rx;
|
||||
run_until(
|
||||
&mut self.run,
|
||||
async { from_queue_rx.next().await.unwrap() }.boxed(),
|
||||
)
|
||||
.await
|
||||
run_until(&mut self.run, async { from_queue_rx.next().await.unwrap() }.boxed()).await
|
||||
}
|
||||
|
||||
fn send_from_pool(&mut self, from_pool: pool::FromPool) {
|
||||
self.from_pool_tx
|
||||
.send(from_pool)
|
||||
.now_or_never()
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
self.from_pool_tx.send(from_pool).now_or_never().unwrap().unwrap();
|
||||
}
|
||||
|
||||
async fn poll_and_recv_to_pool(&mut self) -> pool::ToPool {
|
||||
let to_pool_rx = &mut self.to_pool_rx;
|
||||
run_until(
|
||||
&mut self.run,
|
||||
async { to_pool_rx.next().await.unwrap() }.boxed(),
|
||||
)
|
||||
.await
|
||||
run_until(&mut self.run, async { to_pool_rx.next().await.unwrap() }.boxed()).await
|
||||
}
|
||||
|
||||
async fn poll_ensure_to_pool_is_empty(&mut self) {
|
||||
@@ -655,10 +604,7 @@ mod tests {
|
||||
async fn properly_concludes() {
|
||||
let mut test = Test::new(2, 2);
|
||||
|
||||
test.send_queue(ToQueue::Enqueue {
|
||||
priority: Priority::Background,
|
||||
pvf: pvf(1),
|
||||
});
|
||||
test.send_queue(ToQueue::Enqueue { priority: Priority::Background, pvf: pvf(1) });
|
||||
assert_eq!(test.poll_and_recv_to_pool().await, pool::ToPool::Spawn);
|
||||
|
||||
let w = test.workers.insert(());
|
||||
@@ -675,18 +621,9 @@ mod tests {
|
||||
async fn dont_spawn_over_soft_limit_unless_critical() {
|
||||
let mut test = Test::new(2, 3);
|
||||
|
||||
test.send_queue(ToQueue::Enqueue {
|
||||
priority: Priority::Normal,
|
||||
pvf: pvf(1),
|
||||
});
|
||||
test.send_queue(ToQueue::Enqueue {
|
||||
priority: Priority::Normal,
|
||||
pvf: pvf(2),
|
||||
});
|
||||
test.send_queue(ToQueue::Enqueue {
|
||||
priority: Priority::Normal,
|
||||
pvf: pvf(3),
|
||||
});
|
||||
test.send_queue(ToQueue::Enqueue { priority: Priority::Normal, pvf: pvf(1) });
|
||||
test.send_queue(ToQueue::Enqueue { priority: Priority::Normal, pvf: pvf(2) });
|
||||
test.send_queue(ToQueue::Enqueue { priority: Priority::Normal, pvf: pvf(3) });
|
||||
|
||||
// Receive only two spawns.
|
||||
assert_eq!(test.poll_and_recv_to_pool().await, pool::ToPool::Spawn);
|
||||
@@ -699,27 +636,15 @@ mod tests {
|
||||
test.send_from_pool(pool::FromPool::Spawned(w2));
|
||||
|
||||
// Get two start works.
|
||||
assert_matches!(
|
||||
test.poll_and_recv_to_pool().await,
|
||||
pool::ToPool::StartWork { .. }
|
||||
);
|
||||
assert_matches!(
|
||||
test.poll_and_recv_to_pool().await,
|
||||
pool::ToPool::StartWork { .. }
|
||||
);
|
||||
assert_matches!(test.poll_and_recv_to_pool().await, pool::ToPool::StartWork { .. });
|
||||
assert_matches!(test.poll_and_recv_to_pool().await, pool::ToPool::StartWork { .. });
|
||||
|
||||
test.send_from_pool(pool::FromPool::Concluded(w1, false));
|
||||
|
||||
assert_matches!(
|
||||
test.poll_and_recv_to_pool().await,
|
||||
pool::ToPool::StartWork { .. }
|
||||
);
|
||||
assert_matches!(test.poll_and_recv_to_pool().await, pool::ToPool::StartWork { .. });
|
||||
|
||||
// Enqueue a critical job.
|
||||
test.send_queue(ToQueue::Enqueue {
|
||||
priority: Priority::Critical,
|
||||
pvf: pvf(4),
|
||||
});
|
||||
test.send_queue(ToQueue::Enqueue { priority: Priority::Critical, pvf: pvf(4) });
|
||||
|
||||
// 2 out of 2 are working, but there is a critical job incoming. That means that spawning
|
||||
// another worker is warranted.
|
||||
@@ -730,23 +655,14 @@ mod tests {
|
||||
async fn cull_unwanted() {
|
||||
let mut test = Test::new(1, 2);
|
||||
|
||||
test.send_queue(ToQueue::Enqueue {
|
||||
priority: Priority::Normal,
|
||||
pvf: pvf(1),
|
||||
});
|
||||
test.send_queue(ToQueue::Enqueue { priority: Priority::Normal, pvf: pvf(1) });
|
||||
assert_eq!(test.poll_and_recv_to_pool().await, pool::ToPool::Spawn);
|
||||
let w1 = test.workers.insert(());
|
||||
test.send_from_pool(pool::FromPool::Spawned(w1));
|
||||
assert_matches!(
|
||||
test.poll_and_recv_to_pool().await,
|
||||
pool::ToPool::StartWork { .. }
|
||||
);
|
||||
assert_matches!(test.poll_and_recv_to_pool().await, pool::ToPool::StartWork { .. });
|
||||
|
||||
// Enqueue a critical job, which warrants spawning over the soft limit.
|
||||
test.send_queue(ToQueue::Enqueue {
|
||||
priority: Priority::Critical,
|
||||
pvf: pvf(2),
|
||||
});
|
||||
test.send_queue(ToQueue::Enqueue { priority: Priority::Critical, pvf: pvf(2) });
|
||||
assert_eq!(test.poll_and_recv_to_pool().await, pool::ToPool::Spawn);
|
||||
|
||||
// However, before the new worker had a chance to spawn, the first worker finishes with its
|
||||
@@ -764,47 +680,29 @@ mod tests {
|
||||
async fn bump_prio_on_urgency_change() {
|
||||
let mut test = Test::new(2, 2);
|
||||
|
||||
test.send_queue(ToQueue::Enqueue {
|
||||
priority: Priority::Background,
|
||||
pvf: pvf(1),
|
||||
});
|
||||
test.send_queue(ToQueue::Enqueue { priority: Priority::Background, pvf: pvf(1) });
|
||||
|
||||
assert_eq!(test.poll_and_recv_to_pool().await, pool::ToPool::Spawn);
|
||||
|
||||
let w = test.workers.insert(());
|
||||
test.send_from_pool(pool::FromPool::Spawned(w));
|
||||
|
||||
assert_matches!(
|
||||
test.poll_and_recv_to_pool().await,
|
||||
pool::ToPool::StartWork { .. }
|
||||
);
|
||||
assert_matches!(test.poll_and_recv_to_pool().await, pool::ToPool::StartWork { .. });
|
||||
test.send_queue(ToQueue::Amend {
|
||||
priority: Priority::Normal,
|
||||
artifact_id: pvf(1).as_artifact_id(),
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
test.poll_and_recv_to_pool().await,
|
||||
pool::ToPool::BumpPriority(w)
|
||||
);
|
||||
assert_eq!(test.poll_and_recv_to_pool().await, pool::ToPool::BumpPriority(w));
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn worker_mass_die_out_doesnt_stall_queue() {
|
||||
let mut test = Test::new(2, 2);
|
||||
|
||||
test.send_queue(ToQueue::Enqueue {
|
||||
priority: Priority::Normal,
|
||||
pvf: pvf(1),
|
||||
});
|
||||
test.send_queue(ToQueue::Enqueue {
|
||||
priority: Priority::Normal,
|
||||
pvf: pvf(2),
|
||||
});
|
||||
test.send_queue(ToQueue::Enqueue {
|
||||
priority: Priority::Normal,
|
||||
pvf: pvf(3),
|
||||
});
|
||||
test.send_queue(ToQueue::Enqueue { priority: Priority::Normal, pvf: pvf(1) });
|
||||
test.send_queue(ToQueue::Enqueue { priority: Priority::Normal, pvf: pvf(2) });
|
||||
test.send_queue(ToQueue::Enqueue { priority: Priority::Normal, pvf: pvf(3) });
|
||||
|
||||
assert_eq!(test.poll_and_recv_to_pool().await, pool::ToPool::Spawn);
|
||||
assert_eq!(test.poll_and_recv_to_pool().await, pool::ToPool::Spawn);
|
||||
@@ -815,14 +713,8 @@ mod tests {
|
||||
test.send_from_pool(pool::FromPool::Spawned(w1));
|
||||
test.send_from_pool(pool::FromPool::Spawned(w2));
|
||||
|
||||
assert_matches!(
|
||||
test.poll_and_recv_to_pool().await,
|
||||
pool::ToPool::StartWork { .. }
|
||||
);
|
||||
assert_matches!(
|
||||
test.poll_and_recv_to_pool().await,
|
||||
pool::ToPool::StartWork { .. }
|
||||
);
|
||||
assert_matches!(test.poll_and_recv_to_pool().await, pool::ToPool::StartWork { .. });
|
||||
assert_matches!(test.poll_and_recv_to_pool().await, pool::ToPool::StartWork { .. });
|
||||
|
||||
// Conclude worker 1 and rip it.
|
||||
test.send_from_pool(pool::FromPool::Concluded(w1, true));
|
||||
@@ -840,20 +732,14 @@ mod tests {
|
||||
async fn doesnt_resurrect_ripped_worker_if_no_work() {
|
||||
let mut test = Test::new(2, 2);
|
||||
|
||||
test.send_queue(ToQueue::Enqueue {
|
||||
priority: Priority::Normal,
|
||||
pvf: pvf(1),
|
||||
});
|
||||
test.send_queue(ToQueue::Enqueue { priority: Priority::Normal, pvf: pvf(1) });
|
||||
|
||||
assert_eq!(test.poll_and_recv_to_pool().await, pool::ToPool::Spawn);
|
||||
|
||||
let w1 = test.workers.insert(());
|
||||
test.send_from_pool(pool::FromPool::Spawned(w1));
|
||||
|
||||
assert_matches!(
|
||||
test.poll_and_recv_to_pool().await,
|
||||
pool::ToPool::StartWork { .. }
|
||||
);
|
||||
assert_matches!(test.poll_and_recv_to_pool().await, pool::ToPool::StartWork { .. });
|
||||
|
||||
test.send_from_pool(pool::FromPool::Concluded(w1, true));
|
||||
test.poll_ensure_to_pool_is_empty().await;
|
||||
@@ -863,10 +749,7 @@ mod tests {
|
||||
async fn rip_for_start_work() {
|
||||
let mut test = Test::new(2, 2);
|
||||
|
||||
test.send_queue(ToQueue::Enqueue {
|
||||
priority: Priority::Normal,
|
||||
pvf: pvf(1),
|
||||
});
|
||||
test.send_queue(ToQueue::Enqueue { priority: Priority::Normal, pvf: pvf(1) });
|
||||
|
||||
assert_eq!(test.poll_and_recv_to_pool().await, pool::ToPool::Spawn);
|
||||
|
||||
@@ -875,10 +758,7 @@ mod tests {
|
||||
|
||||
// Now, to the interesting part. After the queue normally issues the start_work command to
|
||||
// the pool, before receiving the command the queue may report that the worker ripped.
|
||||
assert_matches!(
|
||||
test.poll_and_recv_to_pool().await,
|
||||
pool::ToPool::StartWork { .. }
|
||||
);
|
||||
assert_matches!(test.poll_and_recv_to_pool().await, pool::ToPool::StartWork { .. });
|
||||
test.send_from_pool(pool::FromPool::Rip(w1));
|
||||
|
||||
// In this case, the pool should spawn a new worker and request it to work on the item.
|
||||
@@ -886,9 +766,6 @@ mod tests {
|
||||
|
||||
let w2 = test.workers.insert(());
|
||||
test.send_from_pool(pool::FromPool::Spawned(w2));
|
||||
assert_matches!(
|
||||
test.poll_and_recv_to_pool().await,
|
||||
pool::ToPool::StartWork { .. }
|
||||
);
|
||||
assert_matches!(test.poll_and_recv_to_pool().await, pool::ToPool::StartWork { .. });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,17 +15,17 @@
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{
|
||||
LOG_TARGET,
|
||||
artifacts::Artifact,
|
||||
worker_common::{
|
||||
IdleWorker, SpawnErr, WorkerHandle, bytes_to_path, framed_recv, framed_send, path_to_bytes,
|
||||
spawn_with_program_path, tmpfile_in, worker_event_loop,
|
||||
bytes_to_path, framed_recv, framed_send, path_to_bytes, spawn_with_program_path,
|
||||
tmpfile_in, worker_event_loop, IdleWorker, SpawnErr, WorkerHandle,
|
||||
},
|
||||
LOG_TARGET,
|
||||
};
|
||||
use async_std::{
|
||||
io,
|
||||
os::unix::net::UnixStream,
|
||||
path::{PathBuf, Path},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use futures::FutureExt as _;
|
||||
use futures_timer::Delay;
|
||||
@@ -43,13 +43,7 @@ pub async fn spawn(
|
||||
program_path: &Path,
|
||||
spawn_timeout: Duration,
|
||||
) -> Result<(IdleWorker, WorkerHandle), SpawnErr> {
|
||||
spawn_with_program_path(
|
||||
"prepare",
|
||||
program_path,
|
||||
&["prepare-worker"],
|
||||
spawn_timeout,
|
||||
)
|
||||
.await
|
||||
spawn_with_program_path("prepare", program_path, &["prepare-worker"], spawn_timeout).await
|
||||
}
|
||||
|
||||
pub enum Outcome {
|
||||
@@ -99,7 +93,7 @@ pub async fn start_work(
|
||||
"failed to send a prepare request: {:?}",
|
||||
err,
|
||||
);
|
||||
return Outcome::Unreachable;
|
||||
return Outcome::Unreachable
|
||||
}
|
||||
|
||||
// Wait for the result from the worker, keeping in mind that there may be a timeout, the
|
||||
@@ -172,13 +166,13 @@ pub async fn start_work(
|
||||
Selected::Done => {
|
||||
renice(pid, NICENESS_FOREGROUND);
|
||||
Outcome::Concluded(IdleWorker { stream, pid })
|
||||
}
|
||||
},
|
||||
Selected::IoErr | Selected::Deadline => {
|
||||
let bytes = Artifact::DidntMakeIt.serialize();
|
||||
// best effort: there is nothing we can do here if the write fails.
|
||||
let _ = async_std::fs::write(&artifact_path, &bytes).await;
|
||||
Outcome::DidntMakeIt
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
.await
|
||||
@@ -202,8 +196,8 @@ where
|
||||
"failed to create a temp file for the artifact: {:?}",
|
||||
err,
|
||||
);
|
||||
return Outcome::DidntMakeIt;
|
||||
}
|
||||
return Outcome::DidntMakeIt
|
||||
},
|
||||
};
|
||||
|
||||
let outcome = f(tmp_file.clone()).await;
|
||||
@@ -223,7 +217,7 @@ where
|
||||
"failed to remove the tmp file: {:?}",
|
||||
err,
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
outcome
|
||||
@@ -304,9 +298,7 @@ pub fn worker_entrypoint(socket_path: &str) {
|
||||
|
||||
fn prepare_artifact(code: &[u8]) -> Artifact {
|
||||
let blob = match crate::executor_intf::prevalidate(code) {
|
||||
Err(err) => {
|
||||
return Artifact::PrevalidationErr(format!("{:?}", err));
|
||||
}
|
||||
Err(err) => return Artifact::PrevalidationErr(format!("{:?}", err)),
|
||||
Ok(b) => b,
|
||||
};
|
||||
|
||||
|
||||
@@ -29,9 +29,10 @@ pub fn validate_candidate(
|
||||
code: &[u8],
|
||||
params: &[u8],
|
||||
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||
use crate::executor_intf::{prevalidate, prepare, execute, TaskExecutor};
|
||||
use crate::executor_intf::{execute, prepare, prevalidate, TaskExecutor};
|
||||
|
||||
let code = sp_maybe_compressed_blob::decompress(code, 10 * 1024 * 1024).expect("Decompressing code failed");
|
||||
let code = sp_maybe_compressed_blob::decompress(code, 10 * 1024 * 1024)
|
||||
.expect("Decompressing code failed");
|
||||
|
||||
let blob = prevalidate(&*code)?;
|
||||
let artifact = prepare(blob)?;
|
||||
@@ -61,15 +62,15 @@ macro_rules! decl_puppet_worker_main {
|
||||
match subcommand.as_ref() {
|
||||
"sleep" => {
|
||||
std::thread::sleep(std::time::Duration::from_secs(5));
|
||||
}
|
||||
},
|
||||
"prepare-worker" => {
|
||||
let socket_path = &args[2];
|
||||
$crate::prepare_worker_entrypoint(socket_path);
|
||||
}
|
||||
},
|
||||
"execute-worker" => {
|
||||
let socket_path = &args[2];
|
||||
$crate::execute_worker_entrypoint(socket_path);
|
||||
}
|
||||
},
|
||||
other => panic!("unknown subcommand: {}", other),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,13 @@ use crate::LOG_TARGET;
|
||||
use async_std::{
|
||||
io,
|
||||
os::unix::net::{UnixListener, UnixStream},
|
||||
path::{PathBuf, Path},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use futures::{
|
||||
AsyncRead, AsyncWrite, AsyncReadExt as _, AsyncWriteExt as _, FutureExt as _, never::Never,
|
||||
never::Never, AsyncRead, AsyncReadExt as _, AsyncWrite, AsyncWriteExt as _, FutureExt as _,
|
||||
};
|
||||
use futures_timer::Delay;
|
||||
use pin_project::pin_project;
|
||||
use rand::Rng;
|
||||
use std::{
|
||||
fmt, mem,
|
||||
@@ -33,7 +34,6 @@ use std::{
|
||||
task::{Context, Poll},
|
||||
time::Duration,
|
||||
};
|
||||
use pin_project::pin_project;
|
||||
|
||||
/// This is publicly exposed only for integration tests.
|
||||
#[doc(hidden)]
|
||||
@@ -47,9 +47,7 @@ pub async fn spawn_with_program_path(
|
||||
with_transient_socket_path(debug_id, |socket_path| {
|
||||
let socket_path = socket_path.to_owned();
|
||||
async move {
|
||||
let listener = UnixListener::bind(&socket_path)
|
||||
.await
|
||||
.map_err(|_| SpawnErr::Bind)?;
|
||||
let listener = UnixListener::bind(&socket_path).await.map_err(|_| SpawnErr::Bind)?;
|
||||
|
||||
let handle = WorkerHandle::spawn(program_path, extra_args, socket_path)
|
||||
.map_err(|_| SpawnErr::ProcessSpawn)?;
|
||||
@@ -97,11 +95,7 @@ pub async fn tmpfile_in(prefix: &str, dir: &Path) -> io::Result<PathBuf> {
|
||||
|
||||
let mut buf = Vec::with_capacity(prefix.len() + DESCRIMINATOR_LEN);
|
||||
buf.extend(prefix.as_bytes());
|
||||
buf.extend(
|
||||
rand::thread_rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(DESCRIMINATOR_LEN),
|
||||
);
|
||||
buf.extend(rand::thread_rng().sample_iter(&Alphanumeric).take(DESCRIMINATOR_LEN));
|
||||
|
||||
let s = std::str::from_utf8(&buf)
|
||||
.expect("the string is collected from a valid utf-8 sequence; qed");
|
||||
@@ -120,9 +114,7 @@ pub async fn tmpfile_in(prefix: &str, dir: &Path) -> io::Result<PathBuf> {
|
||||
}
|
||||
}
|
||||
|
||||
Err(
|
||||
io::Error::new(io::ErrorKind::Other, "failed to create a temporary file")
|
||||
)
|
||||
Err(io::Error::new(io::ErrorKind::Other, "failed to create a temporary file"))
|
||||
}
|
||||
|
||||
/// The same as [`tmpfile_in`], but uses [`std::env::temp_dir`] as the directory.
|
||||
@@ -245,17 +237,17 @@ impl futures::Future for WorkerHandle {
|
||||
Ok(0) => {
|
||||
// 0 means EOF means the child was terminated. Resolve.
|
||||
Poll::Ready(())
|
||||
}
|
||||
},
|
||||
Ok(_bytes_read) => {
|
||||
// weird, we've read something. Pretend that never happened and reschedule ourselves.
|
||||
cx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
// The implementation is guaranteed to not to return WouldBlock and Interrupted. This
|
||||
// leaves us with a legit errors which we suppose were due to termination.
|
||||
Poll::Ready(())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,22 +15,16 @@
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::TestHost;
|
||||
use polkadot_parachain::{
|
||||
primitives::{
|
||||
RelayChainBlockNumber, BlockData as GenericBlockData, HeadData as GenericHeadData,
|
||||
ValidationParams,
|
||||
},
|
||||
};
|
||||
use adder::{hash_state, BlockData, HeadData};
|
||||
use parity_scale_codec::{Decode, Encode};
|
||||
use adder::{HeadData, BlockData, hash_state};
|
||||
use polkadot_parachain::primitives::{
|
||||
BlockData as GenericBlockData, HeadData as GenericHeadData, RelayChainBlockNumber,
|
||||
ValidationParams,
|
||||
};
|
||||
|
||||
#[async_std::test]
|
||||
async fn execute_good_on_parent() {
|
||||
let parent_head = HeadData {
|
||||
number: 0,
|
||||
parent_hash: [0; 32],
|
||||
post_state: hash_state(0),
|
||||
};
|
||||
let parent_head = HeadData { number: 0, parent_hash: [0; 32], post_state: hash_state(0) };
|
||||
|
||||
let block_data = BlockData { state: 0, add: 512 };
|
||||
|
||||
@@ -65,16 +59,9 @@ async fn execute_good_chain_on_parent() {
|
||||
let host = TestHost::new();
|
||||
|
||||
for add in 0..10 {
|
||||
let parent_head = HeadData {
|
||||
number,
|
||||
parent_hash,
|
||||
post_state: hash_state(last_state),
|
||||
};
|
||||
let parent_head = HeadData { number, parent_hash, post_state: hash_state(last_state) };
|
||||
|
||||
let block_data = BlockData {
|
||||
state: last_state,
|
||||
add,
|
||||
};
|
||||
let block_data = BlockData { state: last_state, add };
|
||||
|
||||
let ret = host
|
||||
.validate_candidate(
|
||||
@@ -103,11 +90,7 @@ async fn execute_good_chain_on_parent() {
|
||||
|
||||
#[async_std::test]
|
||||
async fn execute_bad_on_parent() {
|
||||
let parent_head = HeadData {
|
||||
number: 0,
|
||||
parent_hash: [0; 32],
|
||||
post_state: hash_state(0),
|
||||
};
|
||||
let parent_head = HeadData { number: 0, parent_hash: [0; 32], post_state: hash_state(0) };
|
||||
|
||||
let block_data = BlockData {
|
||||
state: 256, // start state is wrong.
|
||||
|
||||
@@ -14,10 +14,12 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use polkadot_node_core_pvf::{Pvf, ValidationHost, start, Config, InvalidCandidate, ValidationError};
|
||||
use polkadot_parachain::primitives::{BlockData, ValidationParams, ValidationResult};
|
||||
use parity_scale_codec::Encode as _;
|
||||
use async_std::sync::Mutex;
|
||||
use parity_scale_codec::Encode as _;
|
||||
use polkadot_node_core_pvf::{
|
||||
start, Config, InvalidCandidate, Pvf, ValidationError, ValidationHost,
|
||||
};
|
||||
use polkadot_parachain::primitives::{BlockData, ValidationParams, ValidationResult};
|
||||
|
||||
mod adder;
|
||||
mod worker_common;
|
||||
@@ -44,10 +46,7 @@ impl TestHost {
|
||||
f(&mut config);
|
||||
let (host, task) = start(config);
|
||||
let _ = async_std::task::spawn(task);
|
||||
Self {
|
||||
_cache_dir: cache_dir,
|
||||
host: Mutex::new(host),
|
||||
}
|
||||
Self { _cache_dir: cache_dir, host: Mutex::new(host) }
|
||||
}
|
||||
|
||||
async fn validate_candidate(
|
||||
@@ -57,7 +56,8 @@ impl TestHost {
|
||||
) -> Result<ValidationResult, ValidationError> {
|
||||
let (result_tx, result_rx) = futures::channel::oneshot::channel();
|
||||
|
||||
let code = sp_maybe_compressed_blob::decompress(code, 16 * 1024 * 1024).expect("Compression works");
|
||||
let code = sp_maybe_compressed_blob::decompress(code, 16 * 1024 * 1024)
|
||||
.expect("Compression works");
|
||||
|
||||
self.host
|
||||
.lock()
|
||||
@@ -91,7 +91,7 @@ async fn terminates_on_timeout() {
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Err(ValidationError::InvalidCandidate(InvalidCandidate::HardTimeout)) => {}
|
||||
Err(ValidationError::InvalidCandidate(InvalidCandidate::HardTimeout)) => {},
|
||||
r => panic!("{:?}", r),
|
||||
}
|
||||
}
|
||||
@@ -124,8 +124,8 @@ async fn parallel_execution() {
|
||||
// total time should be < 2 x EXECUTION_TIMEOUT_SEC
|
||||
const EXECUTION_TIMEOUT_SEC: u64 = 3;
|
||||
assert!(
|
||||
std::time::Instant::now().duration_since(start)
|
||||
< std::time::Duration::from_secs(EXECUTION_TIMEOUT_SEC * 2)
|
||||
std::time::Instant::now().duration_since(start) <
|
||||
std::time::Duration::from_secs(EXECUTION_TIMEOUT_SEC * 2)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,13 +20,9 @@ use std::time::Duration;
|
||||
|
||||
#[async_std::test]
|
||||
async fn spawn_timeout() {
|
||||
let result = spawn_with_program_path(
|
||||
"integration-test",
|
||||
PUPPET_EXE,
|
||||
&["sleep"],
|
||||
Duration::from_secs(2),
|
||||
)
|
||||
.await;
|
||||
let result =
|
||||
spawn_with_program_path("integration-test", PUPPET_EXE, &["sleep"], Duration::from_secs(2))
|
||||
.await;
|
||||
assert!(matches!(result, Err(SpawnErr::AcceptTimeout)));
|
||||
}
|
||||
|
||||
|
||||
@@ -71,18 +71,32 @@ impl<T> ResidentSize for VecOfDoesNotAllocate<T> {
|
||||
pub(crate) struct RequestResultCache {
|
||||
authorities: MemoryLruCache<Hash, VecOfDoesNotAllocate<AuthorityDiscoveryId>>,
|
||||
validators: MemoryLruCache<Hash, ResidentSizeOf<Vec<ValidatorId>>>,
|
||||
validator_groups: MemoryLruCache<Hash, ResidentSizeOf<(Vec<Vec<ValidatorIndex>>, GroupRotationInfo)>>,
|
||||
validator_groups:
|
||||
MemoryLruCache<Hash, ResidentSizeOf<(Vec<Vec<ValidatorIndex>>, GroupRotationInfo)>>,
|
||||
availability_cores: MemoryLruCache<Hash, ResidentSizeOf<Vec<CoreState>>>,
|
||||
persisted_validation_data: MemoryLruCache<(Hash, ParaId, OccupiedCoreAssumption), ResidentSizeOf<Option<PersistedValidationData>>>,
|
||||
check_validation_outputs: MemoryLruCache<(Hash, ParaId, CandidateCommitments), ResidentSizeOf<bool>>,
|
||||
persisted_validation_data: MemoryLruCache<
|
||||
(Hash, ParaId, OccupiedCoreAssumption),
|
||||
ResidentSizeOf<Option<PersistedValidationData>>,
|
||||
>,
|
||||
check_validation_outputs:
|
||||
MemoryLruCache<(Hash, ParaId, CandidateCommitments), ResidentSizeOf<bool>>,
|
||||
session_index_for_child: MemoryLruCache<Hash, ResidentSizeOf<SessionIndex>>,
|
||||
validation_code: MemoryLruCache<(Hash, ParaId, OccupiedCoreAssumption), ResidentSizeOf<Option<ValidationCode>>>,
|
||||
validation_code_by_hash: MemoryLruCache<ValidationCodeHash, ResidentSizeOf<Option<ValidationCode>>>,
|
||||
candidate_pending_availability: MemoryLruCache<(Hash, ParaId), ResidentSizeOf<Option<CommittedCandidateReceipt>>>,
|
||||
validation_code: MemoryLruCache<
|
||||
(Hash, ParaId, OccupiedCoreAssumption),
|
||||
ResidentSizeOf<Option<ValidationCode>>,
|
||||
>,
|
||||
validation_code_by_hash:
|
||||
MemoryLruCache<ValidationCodeHash, ResidentSizeOf<Option<ValidationCode>>>,
|
||||
candidate_pending_availability:
|
||||
MemoryLruCache<(Hash, ParaId), ResidentSizeOf<Option<CommittedCandidateReceipt>>>,
|
||||
candidate_events: MemoryLruCache<Hash, ResidentSizeOf<Vec<CandidateEvent>>>,
|
||||
session_info: MemoryLruCache<SessionIndex, ResidentSizeOf<Option<SessionInfo>>>,
|
||||
dmq_contents: MemoryLruCache<(Hash, ParaId), ResidentSizeOf<Vec<InboundDownwardMessage<BlockNumber>>>>,
|
||||
inbound_hrmp_channels_contents: MemoryLruCache<(Hash, ParaId), ResidentSizeOf<BTreeMap<ParaId, Vec<InboundHrmpMessage<BlockNumber>>>>>,
|
||||
dmq_contents:
|
||||
MemoryLruCache<(Hash, ParaId), ResidentSizeOf<Vec<InboundDownwardMessage<BlockNumber>>>>,
|
||||
inbound_hrmp_channels_contents: MemoryLruCache<
|
||||
(Hash, ParaId),
|
||||
ResidentSizeOf<BTreeMap<ParaId, Vec<InboundHrmpMessage<BlockNumber>>>>,
|
||||
>,
|
||||
current_babe_epoch: MemoryLruCache<Hash, DoesNotAllocate<Epoch>>,
|
||||
}
|
||||
|
||||
@@ -98,7 +112,9 @@ impl Default for RequestResultCache {
|
||||
session_index_for_child: MemoryLruCache::new(SESSION_INDEX_FOR_CHILD_CACHE_SIZE),
|
||||
validation_code: MemoryLruCache::new(VALIDATION_CODE_CACHE_SIZE),
|
||||
validation_code_by_hash: MemoryLruCache::new(VALIDATION_CODE_CACHE_SIZE),
|
||||
candidate_pending_availability: MemoryLruCache::new(CANDIDATE_PENDING_AVAILABILITY_CACHE_SIZE),
|
||||
candidate_pending_availability: MemoryLruCache::new(
|
||||
CANDIDATE_PENDING_AVAILABILITY_CACHE_SIZE,
|
||||
),
|
||||
candidate_events: MemoryLruCache::new(CANDIDATE_EVENTS_CACHE_SIZE),
|
||||
session_info: MemoryLruCache::new(SESSION_INFO_CACHE_SIZE),
|
||||
dmq_contents: MemoryLruCache::new(DMQ_CONTENTS_CACHE_SIZE),
|
||||
@@ -109,11 +125,18 @@ impl Default for RequestResultCache {
|
||||
}
|
||||
|
||||
impl RequestResultCache {
|
||||
pub(crate) fn authorities(&mut self, relay_parent: &Hash) -> Option<&Vec<AuthorityDiscoveryId>> {
|
||||
pub(crate) fn authorities(
|
||||
&mut self,
|
||||
relay_parent: &Hash,
|
||||
) -> Option<&Vec<AuthorityDiscoveryId>> {
|
||||
self.authorities.get(relay_parent).map(|v| &v.0)
|
||||
}
|
||||
|
||||
pub(crate) fn cache_authorities(&mut self, relay_parent: Hash, authorities: Vec<AuthorityDiscoveryId>) {
|
||||
pub(crate) fn cache_authorities(
|
||||
&mut self,
|
||||
relay_parent: Hash,
|
||||
authorities: Vec<AuthorityDiscoveryId>,
|
||||
) {
|
||||
self.authorities.insert(relay_parent, VecOfDoesNotAllocate(authorities));
|
||||
}
|
||||
|
||||
@@ -125,11 +148,18 @@ impl RequestResultCache {
|
||||
self.validators.insert(relay_parent, ResidentSizeOf(validators));
|
||||
}
|
||||
|
||||
pub(crate) fn validator_groups(&mut self, relay_parent: &Hash) -> Option<&(Vec<Vec<ValidatorIndex>>, GroupRotationInfo)> {
|
||||
pub(crate) fn validator_groups(
|
||||
&mut self,
|
||||
relay_parent: &Hash,
|
||||
) -> Option<&(Vec<Vec<ValidatorIndex>>, GroupRotationInfo)> {
|
||||
self.validator_groups.get(relay_parent).map(|v| &v.0)
|
||||
}
|
||||
|
||||
pub(crate) fn cache_validator_groups(&mut self, relay_parent: Hash, groups: (Vec<Vec<ValidatorIndex>>, GroupRotationInfo)) {
|
||||
pub(crate) fn cache_validator_groups(
|
||||
&mut self,
|
||||
relay_parent: Hash,
|
||||
groups: (Vec<Vec<ValidatorIndex>>, GroupRotationInfo),
|
||||
) {
|
||||
self.validator_groups.insert(relay_parent, ResidentSizeOf(groups));
|
||||
}
|
||||
|
||||
@@ -141,19 +171,33 @@ impl RequestResultCache {
|
||||
self.availability_cores.insert(relay_parent, ResidentSizeOf(cores));
|
||||
}
|
||||
|
||||
pub(crate) fn persisted_validation_data(&mut self, key: (Hash, ParaId, OccupiedCoreAssumption)) -> Option<&Option<PersistedValidationData>> {
|
||||
pub(crate) fn persisted_validation_data(
|
||||
&mut self,
|
||||
key: (Hash, ParaId, OccupiedCoreAssumption),
|
||||
) -> Option<&Option<PersistedValidationData>> {
|
||||
self.persisted_validation_data.get(&key).map(|v| &v.0)
|
||||
}
|
||||
|
||||
pub(crate) fn cache_persisted_validation_data(&mut self, key: (Hash, ParaId, OccupiedCoreAssumption), data: Option<PersistedValidationData>) {
|
||||
pub(crate) fn cache_persisted_validation_data(
|
||||
&mut self,
|
||||
key: (Hash, ParaId, OccupiedCoreAssumption),
|
||||
data: Option<PersistedValidationData>,
|
||||
) {
|
||||
self.persisted_validation_data.insert(key, ResidentSizeOf(data));
|
||||
}
|
||||
|
||||
pub(crate) fn check_validation_outputs(&mut self, key: (Hash, ParaId, CandidateCommitments)) -> Option<&bool> {
|
||||
pub(crate) fn check_validation_outputs(
|
||||
&mut self,
|
||||
key: (Hash, ParaId, CandidateCommitments),
|
||||
) -> Option<&bool> {
|
||||
self.check_validation_outputs.get(&key).map(|v| &v.0)
|
||||
}
|
||||
|
||||
pub(crate) fn cache_check_validation_outputs(&mut self, key: (Hash, ParaId, CandidateCommitments), value: bool) {
|
||||
pub(crate) fn cache_check_validation_outputs(
|
||||
&mut self,
|
||||
key: (Hash, ParaId, CandidateCommitments),
|
||||
value: bool,
|
||||
) {
|
||||
self.check_validation_outputs.insert(key, ResidentSizeOf(value));
|
||||
}
|
||||
|
||||
@@ -161,33 +205,58 @@ impl RequestResultCache {
|
||||
self.session_index_for_child.get(relay_parent).map(|v| &v.0)
|
||||
}
|
||||
|
||||
pub(crate) fn cache_session_index_for_child(&mut self, relay_parent: Hash, index: SessionIndex) {
|
||||
pub(crate) fn cache_session_index_for_child(
|
||||
&mut self,
|
||||
relay_parent: Hash,
|
||||
index: SessionIndex,
|
||||
) {
|
||||
self.session_index_for_child.insert(relay_parent, ResidentSizeOf(index));
|
||||
}
|
||||
|
||||
pub(crate) fn validation_code(&mut self, key: (Hash, ParaId, OccupiedCoreAssumption)) -> Option<&Option<ValidationCode>> {
|
||||
pub(crate) fn validation_code(
|
||||
&mut self,
|
||||
key: (Hash, ParaId, OccupiedCoreAssumption),
|
||||
) -> Option<&Option<ValidationCode>> {
|
||||
self.validation_code.get(&key).map(|v| &v.0)
|
||||
}
|
||||
|
||||
pub(crate) fn cache_validation_code(&mut self, key: (Hash, ParaId, OccupiedCoreAssumption), value: Option<ValidationCode>) {
|
||||
pub(crate) fn cache_validation_code(
|
||||
&mut self,
|
||||
key: (Hash, ParaId, OccupiedCoreAssumption),
|
||||
value: Option<ValidationCode>,
|
||||
) {
|
||||
self.validation_code.insert(key, ResidentSizeOf(value));
|
||||
}
|
||||
|
||||
// the actual key is `ValidationCodeHash` (`Hash` is ignored),
|
||||
// but we keep the interface that way to keep the macro simple
|
||||
pub(crate) fn validation_code_by_hash(&mut self, key: (Hash, ValidationCodeHash)) -> Option<&Option<ValidationCode>> {
|
||||
pub(crate) fn validation_code_by_hash(
|
||||
&mut self,
|
||||
key: (Hash, ValidationCodeHash),
|
||||
) -> Option<&Option<ValidationCode>> {
|
||||
self.validation_code_by_hash.get(&key.1).map(|v| &v.0)
|
||||
}
|
||||
|
||||
pub(crate) fn cache_validation_code_by_hash(&mut self, key: ValidationCodeHash, value: Option<ValidationCode>) {
|
||||
pub(crate) fn cache_validation_code_by_hash(
|
||||
&mut self,
|
||||
key: ValidationCodeHash,
|
||||
value: Option<ValidationCode>,
|
||||
) {
|
||||
self.validation_code_by_hash.insert(key, ResidentSizeOf(value));
|
||||
}
|
||||
|
||||
pub(crate) fn candidate_pending_availability(&mut self, key: (Hash, ParaId)) -> Option<&Option<CommittedCandidateReceipt>> {
|
||||
pub(crate) fn candidate_pending_availability(
|
||||
&mut self,
|
||||
key: (Hash, ParaId),
|
||||
) -> Option<&Option<CommittedCandidateReceipt>> {
|
||||
self.candidate_pending_availability.get(&key).map(|v| &v.0)
|
||||
}
|
||||
|
||||
pub(crate) fn cache_candidate_pending_availability(&mut self, key: (Hash, ParaId), value: Option<CommittedCandidateReceipt>) {
|
||||
pub(crate) fn cache_candidate_pending_availability(
|
||||
&mut self,
|
||||
key: (Hash, ParaId),
|
||||
value: Option<CommittedCandidateReceipt>,
|
||||
) {
|
||||
self.candidate_pending_availability.insert(key, ResidentSizeOf(value));
|
||||
}
|
||||
|
||||
@@ -195,11 +264,18 @@ impl RequestResultCache {
|
||||
self.candidate_events.get(relay_parent).map(|v| &v.0)
|
||||
}
|
||||
|
||||
pub(crate) fn cache_candidate_events(&mut self, relay_parent: Hash, events: Vec<CandidateEvent>) {
|
||||
pub(crate) fn cache_candidate_events(
|
||||
&mut self,
|
||||
relay_parent: Hash,
|
||||
events: Vec<CandidateEvent>,
|
||||
) {
|
||||
self.candidate_events.insert(relay_parent, ResidentSizeOf(events));
|
||||
}
|
||||
|
||||
pub(crate) fn session_info(&mut self, key: (Hash, SessionIndex)) -> Option<&Option<SessionInfo>> {
|
||||
pub(crate) fn session_info(
|
||||
&mut self,
|
||||
key: (Hash, SessionIndex),
|
||||
) -> Option<&Option<SessionInfo>> {
|
||||
self.session_info.get(&key.1).map(|v| &v.0)
|
||||
}
|
||||
|
||||
@@ -207,19 +283,33 @@ impl RequestResultCache {
|
||||
self.session_info.insert(key, ResidentSizeOf(value));
|
||||
}
|
||||
|
||||
pub(crate) fn dmq_contents(&mut self, key: (Hash, ParaId)) -> Option<&Vec<InboundDownwardMessage<BlockNumber>>> {
|
||||
pub(crate) fn dmq_contents(
|
||||
&mut self,
|
||||
key: (Hash, ParaId),
|
||||
) -> Option<&Vec<InboundDownwardMessage<BlockNumber>>> {
|
||||
self.dmq_contents.get(&key).map(|v| &v.0)
|
||||
}
|
||||
|
||||
pub(crate) fn cache_dmq_contents(&mut self, key: (Hash, ParaId), value: Vec<InboundDownwardMessage<BlockNumber>>) {
|
||||
pub(crate) fn cache_dmq_contents(
|
||||
&mut self,
|
||||
key: (Hash, ParaId),
|
||||
value: Vec<InboundDownwardMessage<BlockNumber>>,
|
||||
) {
|
||||
self.dmq_contents.insert(key, ResidentSizeOf(value));
|
||||
}
|
||||
|
||||
pub(crate) fn inbound_hrmp_channels_contents(&mut self, key: (Hash, ParaId)) -> Option<&BTreeMap<ParaId, Vec<InboundHrmpMessage<BlockNumber>>>> {
|
||||
pub(crate) fn inbound_hrmp_channels_contents(
|
||||
&mut self,
|
||||
key: (Hash, ParaId),
|
||||
) -> Option<&BTreeMap<ParaId, Vec<InboundHrmpMessage<BlockNumber>>>> {
|
||||
self.inbound_hrmp_channels_contents.get(&key).map(|v| &v.0)
|
||||
}
|
||||
|
||||
pub(crate) fn cache_inbound_hrmp_channel_contents(&mut self, key: (Hash, ParaId), value: BTreeMap<ParaId, Vec<InboundHrmpMessage<BlockNumber>>>) {
|
||||
pub(crate) fn cache_inbound_hrmp_channel_contents(
|
||||
&mut self,
|
||||
key: (Hash, ParaId),
|
||||
value: BTreeMap<ParaId, Vec<InboundHrmpMessage<BlockNumber>>>,
|
||||
) {
|
||||
self.inbound_hrmp_channels_contents.insert(key, ResidentSizeOf(value));
|
||||
}
|
||||
|
||||
@@ -246,6 +336,10 @@ pub(crate) enum RequestResult {
|
||||
CandidateEvents(Hash, Vec<CandidateEvent>),
|
||||
SessionInfo(Hash, SessionIndex, Option<SessionInfo>),
|
||||
DmqContents(Hash, ParaId, Vec<InboundDownwardMessage<BlockNumber>>),
|
||||
InboundHrmpChannelsContents(Hash, ParaId, BTreeMap<ParaId, Vec<InboundHrmpMessage<BlockNumber>>>),
|
||||
InboundHrmpChannelsContents(
|
||||
Hash,
|
||||
ParaId,
|
||||
BTreeMap<ParaId, Vec<InboundHrmpMessage<BlockNumber>>>,
|
||||
),
|
||||
CurrentBabeEpoch(Hash, Epoch),
|
||||
}
|
||||
|
||||
@@ -22,27 +22,23 @@
|
||||
#![deny(unused_crate_dependencies)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use polkadot_subsystem::{
|
||||
SubsystemError, SubsystemResult,
|
||||
FromOverseer, OverseerSignal, SpawnedSubsystem,
|
||||
SubsystemContext,
|
||||
errors::RuntimeApiError,
|
||||
messages::{
|
||||
RuntimeApiMessage, RuntimeApiRequest as Request,
|
||||
},
|
||||
overseer,
|
||||
};
|
||||
use polkadot_node_subsystem_util::metrics::{self, prometheus};
|
||||
use polkadot_primitives::v1::{Block, BlockId, Hash, ParachainHost};
|
||||
use polkadot_subsystem::{
|
||||
errors::RuntimeApiError,
|
||||
messages::{RuntimeApiMessage, RuntimeApiRequest as Request},
|
||||
overseer, FromOverseer, OverseerSignal, SpawnedSubsystem, SubsystemContext, SubsystemError,
|
||||
SubsystemResult,
|
||||
};
|
||||
|
||||
use sp_api::ProvideRuntimeApi;
|
||||
use sp_authority_discovery::AuthorityDiscoveryApi;
|
||||
use sp_core::traits::SpawnNamed;
|
||||
use sp_consensus_babe::BabeApi;
|
||||
use sp_core::traits::SpawnNamed;
|
||||
|
||||
use futures::{prelude::*, stream::FuturesUnordered, channel::oneshot, select};
|
||||
use std::{sync::Arc, collections::VecDeque, pin::Pin};
|
||||
use cache::{RequestResult, RequestResultCache};
|
||||
use futures::{channel::oneshot, prelude::*, select, stream::FuturesUnordered};
|
||||
use std::{collections::VecDeque, pin::Pin, sync::Arc};
|
||||
|
||||
mod cache;
|
||||
|
||||
@@ -75,7 +71,11 @@ pub struct RuntimeApiSubsystem<Client> {
|
||||
|
||||
impl<Client> RuntimeApiSubsystem<Client> {
|
||||
/// Create a new Runtime API subsystem wrapping the given client and metrics.
|
||||
pub fn new(client: Arc<Client>, metrics: Metrics, spawn_handle: impl SpawnNamed + 'static) -> Self {
|
||||
pub fn new(
|
||||
client: Arc<Client>,
|
||||
metrics: Metrics,
|
||||
spawn_handle: impl SpawnNamed + 'static,
|
||||
) -> Self {
|
||||
RuntimeApiSubsystem {
|
||||
client,
|
||||
metrics,
|
||||
@@ -87,21 +87,20 @@ impl<Client> RuntimeApiSubsystem<Client> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<Client, Context> overseer::Subsystem<Context, SubsystemError> for RuntimeApiSubsystem<Client> where
|
||||
impl<Client, Context> overseer::Subsystem<Context, SubsystemError> for RuntimeApiSubsystem<Client>
|
||||
where
|
||||
Client: ProvideRuntimeApi<Block> + Send + 'static + Sync,
|
||||
Client::Api: ParachainHost<Block> + BabeApi<Block> + AuthorityDiscoveryApi<Block>,
|
||||
Context: SubsystemContext<Message = RuntimeApiMessage>,
|
||||
Context: overseer::SubsystemContext<Message = RuntimeApiMessage>,
|
||||
{
|
||||
fn start(self, ctx: Context) -> SpawnedSubsystem {
|
||||
SpawnedSubsystem {
|
||||
future: run(ctx, self).boxed(),
|
||||
name: "runtime-api-subsystem",
|
||||
}
|
||||
SpawnedSubsystem { future: run(ctx, self).boxed(), name: "runtime-api-subsystem" }
|
||||
}
|
||||
}
|
||||
|
||||
impl<Client> RuntimeApiSubsystem<Client> where
|
||||
impl<Client> RuntimeApiSubsystem<Client>
|
||||
where
|
||||
Client: ProvideRuntimeApi<Block> + Send + 'static + Sync,
|
||||
Client::Api: ParachainHost<Block> + BabeApi<Block> + AuthorityDiscoveryApi<Block>,
|
||||
{
|
||||
@@ -117,26 +116,31 @@ impl<Client> RuntimeApiSubsystem<Client> where
|
||||
self.requests_cache.cache_validator_groups(relay_parent, groups),
|
||||
AvailabilityCores(relay_parent, cores) =>
|
||||
self.requests_cache.cache_availability_cores(relay_parent, cores),
|
||||
PersistedValidationData(relay_parent, para_id, assumption, data) =>
|
||||
self.requests_cache.cache_persisted_validation_data((relay_parent, para_id, assumption), data),
|
||||
CheckValidationOutputs(relay_parent, para_id, commitments, b) =>
|
||||
self.requests_cache.cache_check_validation_outputs((relay_parent, para_id, commitments), b),
|
||||
PersistedValidationData(relay_parent, para_id, assumption, data) => self
|
||||
.requests_cache
|
||||
.cache_persisted_validation_data((relay_parent, para_id, assumption), data),
|
||||
CheckValidationOutputs(relay_parent, para_id, commitments, b) => self
|
||||
.requests_cache
|
||||
.cache_check_validation_outputs((relay_parent, para_id, commitments), b),
|
||||
SessionIndexForChild(relay_parent, session_index) =>
|
||||
self.requests_cache.cache_session_index_for_child(relay_parent, session_index),
|
||||
ValidationCode(relay_parent, para_id, assumption, code) =>
|
||||
self.requests_cache.cache_validation_code((relay_parent, para_id, assumption), code),
|
||||
ValidationCode(relay_parent, para_id, assumption, code) => self
|
||||
.requests_cache
|
||||
.cache_validation_code((relay_parent, para_id, assumption), code),
|
||||
ValidationCodeByHash(_relay_parent, validation_code_hash, code) =>
|
||||
self.requests_cache.cache_validation_code_by_hash(validation_code_hash, code),
|
||||
CandidatePendingAvailability(relay_parent, para_id, candidate) =>
|
||||
self.requests_cache.cache_candidate_pending_availability((relay_parent, para_id), candidate),
|
||||
CandidatePendingAvailability(relay_parent, para_id, candidate) => self
|
||||
.requests_cache
|
||||
.cache_candidate_pending_availability((relay_parent, para_id), candidate),
|
||||
CandidateEvents(relay_parent, events) =>
|
||||
self.requests_cache.cache_candidate_events(relay_parent, events),
|
||||
SessionInfo(_relay_parent, session_index, info) =>
|
||||
self.requests_cache.cache_session_info(session_index, info),
|
||||
DmqContents(relay_parent, para_id, messages) =>
|
||||
self.requests_cache.cache_dmq_contents((relay_parent, para_id), messages),
|
||||
InboundHrmpChannelsContents(relay_parent, para_id, contents) =>
|
||||
self.requests_cache.cache_inbound_hrmp_channel_contents((relay_parent, para_id), contents),
|
||||
InboundHrmpChannelsContents(relay_parent, para_id, contents) => self
|
||||
.requests_cache
|
||||
.cache_inbound_hrmp_channel_contents((relay_parent, para_id), contents),
|
||||
CurrentBabeEpoch(relay_parent, epoch) =>
|
||||
self.requests_cache.cache_current_babe_epoch(relay_parent, epoch),
|
||||
}
|
||||
@@ -169,12 +173,12 @@ impl<Client> RuntimeApiSubsystem<Client> where
|
||||
}
|
||||
|
||||
match request {
|
||||
Request::Authorities(sender) => query!(authorities(), sender)
|
||||
.map(|sender| Request::Authorities(sender)),
|
||||
Request::Validators(sender) => query!(validators(), sender)
|
||||
.map(|sender| Request::Validators(sender)),
|
||||
Request::ValidatorGroups(sender) => query!(validator_groups(), sender)
|
||||
.map(|sender| Request::ValidatorGroups(sender)),
|
||||
Request::Authorities(sender) =>
|
||||
query!(authorities(), sender).map(|sender| Request::Authorities(sender)),
|
||||
Request::Validators(sender) =>
|
||||
query!(validators(), sender).map(|sender| Request::Validators(sender)),
|
||||
Request::ValidatorGroups(sender) =>
|
||||
query!(validator_groups(), sender).map(|sender| Request::ValidatorGroups(sender)),
|
||||
Request::AvailabilityCores(sender) => query!(availability_cores(), sender)
|
||||
.map(|sender| Request::AvailabilityCores(sender)),
|
||||
Request::PersistedValidationData(para, assumption, sender) =>
|
||||
@@ -183,9 +187,8 @@ impl<Client> RuntimeApiSubsystem<Client> where
|
||||
Request::CheckValidationOutputs(para, commitments, sender) =>
|
||||
query!(check_validation_outputs(para, commitments), sender)
|
||||
.map(|sender| Request::CheckValidationOutputs(para, commitments, sender)),
|
||||
Request::SessionIndexForChild(sender) =>
|
||||
query!(session_index_for_child(), sender)
|
||||
.map(|sender| Request::SessionIndexForChild(sender)),
|
||||
Request::SessionIndexForChild(sender) => query!(session_index_for_child(), sender)
|
||||
.map(|sender| Request::SessionIndexForChild(sender)),
|
||||
Request::ValidationCode(para, assumption, sender) =>
|
||||
query!(validation_code(para, assumption), sender)
|
||||
.map(|sender| Request::ValidationCode(para, assumption, sender)),
|
||||
@@ -195,18 +198,17 @@ impl<Client> RuntimeApiSubsystem<Client> where
|
||||
Request::CandidatePendingAvailability(para, sender) =>
|
||||
query!(candidate_pending_availability(para), sender)
|
||||
.map(|sender| Request::CandidatePendingAvailability(para, sender)),
|
||||
Request::CandidateEvents(sender) => query!(candidate_events(), sender)
|
||||
.map(|sender| Request::CandidateEvents(sender)),
|
||||
Request::CandidateEvents(sender) =>
|
||||
query!(candidate_events(), sender).map(|sender| Request::CandidateEvents(sender)),
|
||||
Request::SessionInfo(index, sender) => query!(session_info(index), sender)
|
||||
.map(|sender| Request::SessionInfo(index, sender)),
|
||||
Request::DmqContents(id, sender) => query!(dmq_contents(id), sender)
|
||||
.map(|sender| Request::DmqContents(id, sender)),
|
||||
Request::DmqContents(id, sender) =>
|
||||
query!(dmq_contents(id), sender).map(|sender| Request::DmqContents(id, sender)),
|
||||
Request::InboundHrmpChannelsContents(id, sender) =>
|
||||
query!(inbound_hrmp_channels_contents(id), sender)
|
||||
.map(|sender| Request::InboundHrmpChannelsContents(id, sender)),
|
||||
Request::CurrentBabeEpoch(sender) =>
|
||||
query!(current_babe_epoch(), sender)
|
||||
.map(|sender| Request::CurrentBabeEpoch(sender)),
|
||||
query!(current_babe_epoch(), sender).map(|sender| Request::CurrentBabeEpoch(sender)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,14 +226,10 @@ impl<Client> RuntimeApiSubsystem<Client> where
|
||||
};
|
||||
|
||||
let request = async move {
|
||||
let result = make_runtime_api_request(
|
||||
client,
|
||||
metrics,
|
||||
relay_parent,
|
||||
request,
|
||||
);
|
||||
let result = make_runtime_api_request(client, metrics, relay_parent, request);
|
||||
let _ = sender.send(result);
|
||||
}.boxed();
|
||||
}
|
||||
.boxed();
|
||||
|
||||
if self.active_requests.len() >= MAX_PARALLEL_REQUESTS {
|
||||
self.waiting_requests.push_back((request, receiver));
|
||||
@@ -271,7 +269,8 @@ impl<Client> RuntimeApiSubsystem<Client> where
|
||||
async fn run<Client, Context>(
|
||||
mut ctx: Context,
|
||||
mut subsystem: RuntimeApiSubsystem<Client>,
|
||||
) -> SubsystemResult<()> where
|
||||
) -> SubsystemResult<()>
|
||||
where
|
||||
Client: ProvideRuntimeApi<Block> + Send + Sync + 'static,
|
||||
Client::Api: ParachainHost<Block> + BabeApi<Block> + AuthorityDiscoveryApi<Block>,
|
||||
Context: SubsystemContext<Message = RuntimeApiMessage>,
|
||||
@@ -323,12 +322,14 @@ where
|
||||
Request::Authorities(sender) => query!(Authorities, authorities(), sender),
|
||||
Request::Validators(sender) => query!(Validators, validators(), sender),
|
||||
Request::ValidatorGroups(sender) => query!(ValidatorGroups, validator_groups(), sender),
|
||||
Request::AvailabilityCores(sender) => query!(AvailabilityCores, availability_cores(), sender),
|
||||
Request::AvailabilityCores(sender) =>
|
||||
query!(AvailabilityCores, availability_cores(), sender),
|
||||
Request::PersistedValidationData(para, assumption, sender) =>
|
||||
query!(PersistedValidationData, persisted_validation_data(para, assumption), sender),
|
||||
Request::CheckValidationOutputs(para, commitments, sender) =>
|
||||
query!(CheckValidationOutputs, check_validation_outputs(para, commitments), sender),
|
||||
Request::SessionIndexForChild(sender) => query!(SessionIndexForChild, session_index_for_child(), sender),
|
||||
Request::SessionIndexForChild(sender) =>
|
||||
query!(SessionIndexForChild, session_index_for_child(), sender),
|
||||
Request::ValidationCode(para, assumption, sender) =>
|
||||
query!(ValidationCode, validation_code(para, assumption), sender),
|
||||
Request::ValidationCodeByHash(validation_code_hash, sender) =>
|
||||
@@ -338,7 +339,8 @@ where
|
||||
Request::CandidateEvents(sender) => query!(CandidateEvents, candidate_events(), sender),
|
||||
Request::SessionInfo(index, sender) => query!(SessionInfo, session_info(index), sender),
|
||||
Request::DmqContents(id, sender) => query!(DmqContents, dmq_contents(id), sender),
|
||||
Request::InboundHrmpChannelsContents(id, sender) => query!(InboundHrmpChannelsContents, inbound_hrmp_channels_contents(id), sender),
|
||||
Request::InboundHrmpChannelsContents(id, sender) =>
|
||||
query!(InboundHrmpChannelsContents, inbound_hrmp_channels_contents(id), sender),
|
||||
Request::CurrentBabeEpoch(sender) => query!(CurrentBabeEpoch, current_epoch(), sender),
|
||||
}
|
||||
}
|
||||
@@ -365,12 +367,15 @@ impl Metrics {
|
||||
}
|
||||
|
||||
fn on_cached_request(&self) {
|
||||
self.0.as_ref()
|
||||
self.0
|
||||
.as_ref()
|
||||
.map(|metrics| metrics.chain_api_requests.with_label_values(&["cached"]).inc());
|
||||
}
|
||||
|
||||
/// Provide a timer for `make_runtime_api_request` which observes on drop.
|
||||
fn time_make_runtime_api_request(&self) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
|
||||
fn time_make_runtime_api_request(
|
||||
&self,
|
||||
) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
|
||||
self.0.as_ref().map(|metrics| metrics.make_runtime_api_request.start_timer())
|
||||
}
|
||||
}
|
||||
@@ -389,12 +394,10 @@ impl metrics::Metrics for Metrics {
|
||||
registry,
|
||||
)?,
|
||||
make_runtime_api_request: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_runtime_api_make_runtime_api_request",
|
||||
"Time spent within `runtime_api::make_runtime_api_request`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_runtime_api_make_runtime_api_request",
|
||||
"Time spent within `runtime_api::make_runtime_api_request`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
};
|
||||
|
||||
@@ -16,18 +16,19 @@
|
||||
|
||||
use super::*;
|
||||
|
||||
use polkadot_primitives::v1::{
|
||||
ValidatorId, ValidatorIndex, GroupRotationInfo, CoreState, PersistedValidationData,
|
||||
Id as ParaId, OccupiedCoreAssumption, SessionIndex, ValidationCode,
|
||||
CommittedCandidateReceipt, CandidateEvent, InboundDownwardMessage,
|
||||
InboundHrmpMessage, SessionInfo, AuthorityDiscoveryId, ValidationCodeHash,
|
||||
};
|
||||
use polkadot_node_subsystem_test_helpers as test_helpers;
|
||||
use sp_core::testing::TaskExecutor;
|
||||
use std::{collections::{HashMap, BTreeMap}, sync::{Arc, Mutex}};
|
||||
use futures::channel::oneshot;
|
||||
use polkadot_node_primitives::{
|
||||
BabeEpoch, BabeEpochConfiguration, BabeAllowedSlots,
|
||||
use polkadot_node_primitives::{BabeAllowedSlots, BabeEpoch, BabeEpochConfiguration};
|
||||
use polkadot_node_subsystem_test_helpers as test_helpers;
|
||||
use polkadot_primitives::v1::{
|
||||
AuthorityDiscoveryId, CandidateEvent, CommittedCandidateReceipt, CoreState, GroupRotationInfo,
|
||||
Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, OccupiedCoreAssumption,
|
||||
PersistedValidationData, SessionIndex, SessionInfo, ValidationCode, ValidationCodeHash,
|
||||
ValidatorId, ValidatorIndex,
|
||||
};
|
||||
use sp_core::testing::TaskExecutor;
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
@@ -201,9 +202,11 @@ fn requests_authorities() {
|
||||
let test_task = async move {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
ctx_handle.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(relay_parent, Request::Authorities(tx))
|
||||
}).await;
|
||||
ctx_handle
|
||||
.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(relay_parent, Request::Authorities(tx)),
|
||||
})
|
||||
.await;
|
||||
|
||||
assert_eq!(rx.await.unwrap().unwrap(), runtime_api.authorities);
|
||||
|
||||
@@ -225,9 +228,11 @@ fn requests_validators() {
|
||||
let test_task = async move {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
ctx_handle.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(relay_parent, Request::Validators(tx))
|
||||
}).await;
|
||||
ctx_handle
|
||||
.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(relay_parent, Request::Validators(tx)),
|
||||
})
|
||||
.await;
|
||||
|
||||
assert_eq!(rx.await.unwrap().unwrap(), runtime_api.validators);
|
||||
|
||||
@@ -249,9 +254,11 @@ fn requests_validator_groups() {
|
||||
let test_task = async move {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
ctx_handle.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(relay_parent, Request::ValidatorGroups(tx))
|
||||
}).await;
|
||||
ctx_handle
|
||||
.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(relay_parent, Request::ValidatorGroups(tx)),
|
||||
})
|
||||
.await;
|
||||
|
||||
assert_eq!(rx.await.unwrap().unwrap().0, runtime_api.validator_groups);
|
||||
|
||||
@@ -273,9 +280,11 @@ fn requests_availability_cores() {
|
||||
let test_task = async move {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
ctx_handle.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(relay_parent, Request::AvailabilityCores(tx))
|
||||
}).await;
|
||||
ctx_handle
|
||||
.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(relay_parent, Request::AvailabilityCores(tx)),
|
||||
})
|
||||
.await;
|
||||
|
||||
assert_eq!(rx.await.unwrap().unwrap(), runtime_api.availability_cores);
|
||||
|
||||
@@ -302,22 +311,26 @@ fn requests_persisted_validation_data() {
|
||||
let test_task = async move {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
ctx_handle.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
Request::PersistedValidationData(para_a, OccupiedCoreAssumption::Included, tx)
|
||||
),
|
||||
}).await;
|
||||
ctx_handle
|
||||
.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
Request::PersistedValidationData(para_a, OccupiedCoreAssumption::Included, tx),
|
||||
),
|
||||
})
|
||||
.await;
|
||||
|
||||
assert_eq!(rx.await.unwrap().unwrap(), Some(Default::default()));
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
ctx_handle.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
Request::PersistedValidationData(para_b, OccupiedCoreAssumption::Included, tx)
|
||||
),
|
||||
}).await;
|
||||
ctx_handle
|
||||
.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
Request::PersistedValidationData(para_b, OccupiedCoreAssumption::Included, tx),
|
||||
),
|
||||
})
|
||||
.await;
|
||||
|
||||
assert_eq!(rx.await.unwrap().unwrap(), None);
|
||||
|
||||
@@ -347,36 +360,26 @@ fn requests_check_validation_outputs() {
|
||||
let test_task = async move {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
ctx_handle.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
Request::CheckValidationOutputs(
|
||||
para_a,
|
||||
commitments.clone(),
|
||||
tx,
|
||||
ctx_handle
|
||||
.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
Request::CheckValidationOutputs(para_a, commitments.clone(), tx),
|
||||
),
|
||||
)
|
||||
}).await;
|
||||
assert_eq!(
|
||||
rx.await.unwrap().unwrap(),
|
||||
runtime_api.validation_outputs_results[¶_a],
|
||||
);
|
||||
})
|
||||
.await;
|
||||
assert_eq!(rx.await.unwrap().unwrap(), runtime_api.validation_outputs_results[¶_a],);
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
ctx_handle.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
Request::CheckValidationOutputs(
|
||||
para_b,
|
||||
commitments,
|
||||
tx,
|
||||
ctx_handle
|
||||
.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
Request::CheckValidationOutputs(para_b, commitments, tx),
|
||||
),
|
||||
)
|
||||
}).await;
|
||||
assert_eq!(
|
||||
rx.await.unwrap().unwrap(),
|
||||
runtime_api.validation_outputs_results[¶_b],
|
||||
);
|
||||
})
|
||||
.await;
|
||||
assert_eq!(rx.await.unwrap().unwrap(), runtime_api.validation_outputs_results[¶_b],);
|
||||
|
||||
ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await;
|
||||
};
|
||||
@@ -396,9 +399,11 @@ fn requests_session_index_for_child() {
|
||||
let test_task = async move {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
ctx_handle.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(relay_parent, Request::SessionIndexForChild(tx))
|
||||
}).await;
|
||||
ctx_handle
|
||||
.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(relay_parent, Request::SessionIndexForChild(tx)),
|
||||
})
|
||||
.await;
|
||||
|
||||
assert_eq!(rx.await.unwrap().unwrap(), runtime_api.session_index_for_child);
|
||||
|
||||
@@ -424,9 +429,14 @@ fn requests_session_info() {
|
||||
let test_task = async move {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
ctx_handle.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(relay_parent, Request::SessionInfo(session_index, tx))
|
||||
}).await;
|
||||
ctx_handle
|
||||
.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
Request::SessionInfo(session_index, tx),
|
||||
),
|
||||
})
|
||||
.await;
|
||||
|
||||
assert_eq!(rx.await.unwrap().unwrap(), Some(Default::default()));
|
||||
|
||||
@@ -454,22 +464,26 @@ fn requests_validation_code() {
|
||||
let test_task = async move {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
ctx_handle.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
Request::ValidationCode(para_a, OccupiedCoreAssumption::Included, tx)
|
||||
),
|
||||
}).await;
|
||||
ctx_handle
|
||||
.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
Request::ValidationCode(para_a, OccupiedCoreAssumption::Included, tx),
|
||||
),
|
||||
})
|
||||
.await;
|
||||
|
||||
assert_eq!(rx.await.unwrap().unwrap(), Some(Default::default()));
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
ctx_handle.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
Request::ValidationCode(para_b, OccupiedCoreAssumption::Included, tx)
|
||||
),
|
||||
}).await;
|
||||
ctx_handle
|
||||
.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
Request::ValidationCode(para_b, OccupiedCoreAssumption::Included, tx),
|
||||
),
|
||||
})
|
||||
.await;
|
||||
|
||||
assert_eq!(rx.await.unwrap().unwrap(), None);
|
||||
|
||||
@@ -496,23 +510,27 @@ fn requests_candidate_pending_availability() {
|
||||
let test_task = async move {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
ctx_handle.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
Request::CandidatePendingAvailability(para_a, tx),
|
||||
)
|
||||
}).await;
|
||||
ctx_handle
|
||||
.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
Request::CandidatePendingAvailability(para_a, tx),
|
||||
),
|
||||
})
|
||||
.await;
|
||||
|
||||
assert_eq!(rx.await.unwrap().unwrap(), Some(Default::default()));
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
ctx_handle.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
Request::CandidatePendingAvailability(para_b, tx),
|
||||
)
|
||||
}).await;
|
||||
ctx_handle
|
||||
.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
Request::CandidatePendingAvailability(para_b, tx),
|
||||
),
|
||||
})
|
||||
.await;
|
||||
|
||||
assert_eq!(rx.await.unwrap().unwrap(), None);
|
||||
|
||||
@@ -534,9 +552,11 @@ fn requests_candidate_events() {
|
||||
let test_task = async move {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
ctx_handle.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(relay_parent, Request::CandidateEvents(tx))
|
||||
}).await;
|
||||
ctx_handle
|
||||
.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(relay_parent, Request::CandidateEvents(tx)),
|
||||
})
|
||||
.await;
|
||||
|
||||
assert_eq!(rx.await.unwrap().unwrap(), runtime_api.candidate_events);
|
||||
|
||||
@@ -561,10 +581,7 @@ fn requests_dmq_contents() {
|
||||
runtime_api.dmq_contents.insert(para_a, vec![]);
|
||||
runtime_api.dmq_contents.insert(
|
||||
para_b,
|
||||
vec![InboundDownwardMessage {
|
||||
sent_at: 228,
|
||||
msg: b"Novus Ordo Seclorum".to_vec(),
|
||||
}],
|
||||
vec![InboundDownwardMessage { sent_at: 228, msg: b"Novus Ordo Seclorum".to_vec() }],
|
||||
);
|
||||
|
||||
runtime_api
|
||||
@@ -589,15 +606,10 @@ fn requests_dmq_contents() {
|
||||
.await;
|
||||
assert_eq!(
|
||||
rx.await.unwrap().unwrap(),
|
||||
vec![InboundDownwardMessage {
|
||||
sent_at: 228,
|
||||
msg: b"Novus Ordo Seclorum".to_vec(),
|
||||
}]
|
||||
vec![InboundDownwardMessage { sent_at: 228, msg: b"Novus Ordo Seclorum".to_vec() }]
|
||||
);
|
||||
|
||||
ctx_handle
|
||||
.send(FromOverseer::Signal(OverseerSignal::Conclude))
|
||||
.await;
|
||||
ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await;
|
||||
};
|
||||
futures::executor::block_on(future::join(subsystem_task, test_task));
|
||||
}
|
||||
@@ -614,13 +626,7 @@ fn requests_inbound_hrmp_channels_contents() {
|
||||
|
||||
let para_b_inbound_channels = [
|
||||
(para_a, vec![]),
|
||||
(
|
||||
para_c,
|
||||
vec![InboundHrmpMessage {
|
||||
sent_at: 1,
|
||||
data: "𝙀=𝙈𝘾²".as_bytes().to_owned(),
|
||||
}],
|
||||
),
|
||||
(para_c, vec![InboundHrmpMessage { sent_at: 1, data: "𝙀=𝙈𝘾²".as_bytes().to_owned() }]),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
@@ -630,9 +636,7 @@ fn requests_inbound_hrmp_channels_contents() {
|
||||
let mut runtime_api = MockRuntimeApi::default();
|
||||
|
||||
runtime_api.hrmp_channels.insert(para_a, BTreeMap::new());
|
||||
runtime_api
|
||||
.hrmp_channels
|
||||
.insert(para_b, para_b_inbound_channels.clone());
|
||||
runtime_api.hrmp_channels.insert(para_b, para_b_inbound_channels.clone());
|
||||
|
||||
runtime_api
|
||||
});
|
||||
@@ -662,9 +666,7 @@ fn requests_inbound_hrmp_channels_contents() {
|
||||
.await;
|
||||
assert_eq!(rx.await.unwrap().unwrap(), para_b_inbound_channels,);
|
||||
|
||||
ctx_handle
|
||||
.send(FromOverseer::Signal(OverseerSignal::Conclude))
|
||||
.await;
|
||||
ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await;
|
||||
};
|
||||
futures::executor::block_on(future::join(subsystem_task, test_task));
|
||||
}
|
||||
@@ -680,10 +682,7 @@ fn requests_validation_code_by_hash() {
|
||||
|
||||
for n in 0..5 {
|
||||
let code = ValidationCode::from(vec![n; 32]);
|
||||
runtime_api.validation_code_by_hash.insert(
|
||||
code.hash(),
|
||||
code.clone(),
|
||||
);
|
||||
runtime_api.validation_code_by_hash.insert(code.hash(), code.clone());
|
||||
validation_code.push(code);
|
||||
}
|
||||
|
||||
@@ -697,12 +696,14 @@ fn requests_validation_code_by_hash() {
|
||||
let test_task = async move {
|
||||
for code in validation_code {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
ctx_handle.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
Request::ValidationCodeByHash(code.hash(), tx),
|
||||
)
|
||||
}).await;
|
||||
ctx_handle
|
||||
.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
Request::ValidationCodeByHash(code.hash(), tx),
|
||||
),
|
||||
})
|
||||
.await;
|
||||
|
||||
assert_eq!(rx.await.unwrap().unwrap(), Some(code));
|
||||
}
|
||||
@@ -732,9 +733,11 @@ fn multiple_requests_in_parallel_are_working() {
|
||||
for _ in 0..MAX_PARALLEL_REQUESTS * 10 {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
ctx_handle.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(relay_parent, Request::AvailabilityCores(tx))
|
||||
}).await;
|
||||
ctx_handle
|
||||
.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(relay_parent, Request::AvailabilityCores(tx)),
|
||||
})
|
||||
.await;
|
||||
|
||||
receivers.push(rx);
|
||||
}
|
||||
@@ -763,10 +766,7 @@ fn request_babe_epoch() {
|
||||
duration: 10,
|
||||
authorities: Vec::new(),
|
||||
randomness: [1u8; 32],
|
||||
config: BabeEpochConfiguration {
|
||||
c: (1, 4),
|
||||
allowed_slots: BabeAllowedSlots::PrimarySlots,
|
||||
},
|
||||
config: BabeEpochConfiguration { c: (1, 4), allowed_slots: BabeAllowedSlots::PrimarySlots },
|
||||
};
|
||||
runtime_api.babe_epoch = Some(epoch.clone());
|
||||
let runtime_api = Arc::new(runtime_api);
|
||||
@@ -778,9 +778,11 @@ fn request_babe_epoch() {
|
||||
let test_task = async move {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
ctx_handle.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(relay_parent, Request::CurrentBabeEpoch(tx))
|
||||
}).await;
|
||||
ctx_handle
|
||||
.send(FromOverseer::Communication {
|
||||
msg: RuntimeApiMessage::Request(relay_parent, Request::CurrentBabeEpoch(tx)),
|
||||
})
|
||||
.await;
|
||||
|
||||
assert_eq!(rx.await.unwrap().unwrap(), epoch);
|
||||
ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await;
|
||||
|
||||
@@ -50,9 +50,11 @@ mod config;
|
||||
mod errors;
|
||||
mod spans;
|
||||
|
||||
pub use self::config::{JaegerConfig, JaegerConfigBuilder};
|
||||
pub use self::errors::JaegerError;
|
||||
pub use self::spans::{PerLeafSpan, Span, Stage};
|
||||
pub use self::{
|
||||
config::{JaegerConfig, JaegerConfigBuilder},
|
||||
errors::JaegerError,
|
||||
spans::{PerLeafSpan, Span, Stage},
|
||||
};
|
||||
|
||||
use self::spans::TraceIdentifier;
|
||||
|
||||
@@ -103,8 +105,9 @@ impl Jaeger {
|
||||
|
||||
log::info!("🐹 Collecting jaeger spans for {:?}", &jaeger_agent);
|
||||
|
||||
let (traces_in, mut traces_out) =
|
||||
mick_jaeger::init(mick_jaeger::Config { service_name: format!("polkadot-{}", cfg.node_name) });
|
||||
let (traces_in, mut traces_out) = mick_jaeger::init(mick_jaeger::Config {
|
||||
service_name: format!("polkadot-{}", cfg.node_name),
|
||||
});
|
||||
|
||||
// Spawn a background task that pulls span information and sends them on the network.
|
||||
spawner.spawn(
|
||||
@@ -120,7 +123,7 @@ impl Jaeger {
|
||||
},
|
||||
Err(e) => {
|
||||
log::warn!(target: "jaeger", "UDP socket open error: {}", e);
|
||||
}
|
||||
},
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -84,12 +84,13 @@
|
||||
//! ```
|
||||
|
||||
use parity_scale_codec::Encode;
|
||||
use polkadot_primitives::v1::{BlakeTwo256, CandidateHash, Hash, HashT, Id as ParaId, ValidatorIndex};
|
||||
use polkadot_node_primitives::PoV;
|
||||
use polkadot_primitives::v1::{
|
||||
BlakeTwo256, CandidateHash, Hash, HashT, Id as ParaId, ValidatorIndex,
|
||||
};
|
||||
use sc_network::PeerId;
|
||||
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, sync::Arc};
|
||||
|
||||
use super::INSTANCE;
|
||||
|
||||
@@ -240,7 +241,10 @@ impl Span {
|
||||
/// Creates a new span builder based on anything that can be lazily evaluated
|
||||
/// to and identifier.
|
||||
pub fn new<I: LazyIdent>(identifier: I, span_name: &'static str) -> Span {
|
||||
let mut span = INSTANCE.read_recursive().span(|| <I as LazyIdent>::eval(&identifier), span_name).into();
|
||||
let mut span = INSTANCE
|
||||
.read_recursive()
|
||||
.span(|| <I as LazyIdent>::eval(&identifier), span_name)
|
||||
.into();
|
||||
<I as LazyIdent>::extra_tags(&identifier, &mut span);
|
||||
span
|
||||
}
|
||||
@@ -349,8 +353,9 @@ impl Span {
|
||||
#[inline(always)]
|
||||
pub fn add_follows_from(&mut self, other: &Self) {
|
||||
match (self, other) {
|
||||
(Self::Enabled(ref mut inner), Self::Enabled(ref other_inner)) => inner.add_follows_from(&other_inner),
|
||||
_ => {}
|
||||
(Self::Enabled(ref mut inner), Self::Enabled(ref other_inner)) =>
|
||||
inner.add_follows_from(&other_inner),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,29 +377,30 @@ impl Span {
|
||||
pub fn add_string_tag<V: ToString>(&mut self, tag: &'static str, val: V) {
|
||||
match self {
|
||||
Self::Enabled(ref mut inner) => inner.add_string_tag(tag, val.to_string().as_str()),
|
||||
Self::Disabled => {}
|
||||
Self::Disabled => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a string tag, without consuming the span.
|
||||
pub fn add_string_fmt_debug_tag<V: fmt::Debug>(&mut self, tag: &'static str, val: V) {
|
||||
match self {
|
||||
Self::Enabled(ref mut inner) => inner.add_string_tag(tag, format!("{:?}", val).as_str()),
|
||||
Self::Disabled => {}
|
||||
Self::Enabled(ref mut inner) =>
|
||||
inner.add_string_tag(tag, format!("{:?}", val).as_str()),
|
||||
Self::Disabled => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_int_tag(&mut self, tag: &'static str, value: i64) {
|
||||
match self {
|
||||
Self::Enabled(ref mut inner) => inner.add_int_tag(tag, value),
|
||||
Self::Disabled => {}
|
||||
Self::Disabled => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_uint_tag(&mut self, tag: &'static str, value: u64) {
|
||||
match self {
|
||||
Self::Enabled(ref mut inner) => inner.add_int_tag(tag, value as i64),
|
||||
Self::Disabled => {}
|
||||
Self::Disabled => {},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,8 @@
|
||||
//! messages on the overseer level.
|
||||
|
||||
use polkadot_node_subsystem::*;
|
||||
pub use polkadot_node_subsystem::{overseer, messages::AllMessages, FromOverseer};
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
pub use polkadot_node_subsystem::{messages::AllMessages, overseer, FromOverseer};
|
||||
use std::{future::Future, pin::Pin};
|
||||
|
||||
/// Filter incoming and outgoing messages.
|
||||
pub trait MsgFilter: Send + Sync + Clone + 'static {
|
||||
@@ -95,11 +94,7 @@ where
|
||||
inner: inner.sender().clone(),
|
||||
message_filter: message_filter.clone(),
|
||||
};
|
||||
Self {
|
||||
inner,
|
||||
message_filter,
|
||||
sender,
|
||||
}
|
||||
Self { inner, message_filter, sender }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +103,8 @@ impl<Context, Fil> overseer::SubsystemContext for FilteredContext<Context, Fil>
|
||||
where
|
||||
Context: overseer::SubsystemContext + SubsystemContext,
|
||||
Fil: MsgFilter<Message = <Context as overseer::SubsystemContext>::Message>,
|
||||
<Context as overseer::SubsystemContext>::AllMessages: From<<Context as overseer::SubsystemContext>::Message>,
|
||||
<Context as overseer::SubsystemContext>::AllMessages:
|
||||
From<<Context as overseer::SubsystemContext>::Message>,
|
||||
{
|
||||
type Message = <Context as overseer::SubsystemContext>::Message;
|
||||
type Sender = FilteredSender<<Context as overseer::SubsystemContext>::Sender, Fil>;
|
||||
@@ -120,11 +116,10 @@ where
|
||||
loop {
|
||||
match self.inner.try_recv().await? {
|
||||
None => return Ok(None),
|
||||
Some(msg) => {
|
||||
Some(msg) =>
|
||||
if let Some(msg) = self.message_filter.filter_in(msg) {
|
||||
return Ok(Some(msg));
|
||||
}
|
||||
}
|
||||
return Ok(Some(msg))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,7 +128,7 @@ where
|
||||
loop {
|
||||
let msg = self.inner.recv().await?;
|
||||
if let Some(msg) = self.message_filter.filter_in(msg) {
|
||||
return Ok(msg);
|
||||
return Ok(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,10 +162,7 @@ pub struct FilteredSubsystem<Sub, Fil> {
|
||||
|
||||
impl<Sub, Fil> FilteredSubsystem<Sub, Fil> {
|
||||
pub fn new(subsystem: Sub, message_filter: Fil) -> Self {
|
||||
Self {
|
||||
subsystem,
|
||||
message_filter,
|
||||
}
|
||||
Self { subsystem, message_filter }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,6 +175,9 @@ where
|
||||
{
|
||||
fn start(self, ctx: Context) -> SpawnedSubsystem {
|
||||
let ctx = FilteredContext::new(ctx, self.message_filter);
|
||||
overseer::Subsystem::<FilteredContext<Context, Fil>, SubsystemError>::start(self.subsystem, ctx)
|
||||
overseer::Subsystem::<FilteredContext<Context, Fil>, SubsystemError>::start(
|
||||
self.subsystem,
|
||||
ctx,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +27,7 @@ use polkadot_cli::{
|
||||
create_default_subsystems,
|
||||
service::{
|
||||
AuthorityDiscoveryApi, AuxStore, BabeApi, Block, Error, HeaderBackend, Overseer,
|
||||
OverseerGen, OverseerGenArgs, OverseerHandle, ParachainHost, ProvideRuntimeApi,
|
||||
SpawnNamed,
|
||||
OverseerGen, OverseerGenArgs, OverseerHandle, ParachainHost, ProvideRuntimeApi, SpawnNamed,
|
||||
},
|
||||
Cli,
|
||||
};
|
||||
@@ -42,8 +41,10 @@ use polkadot_node_subsystem_util::metrics::Metrics as _;
|
||||
// Filter wrapping related types.
|
||||
use malus::*;
|
||||
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
};
|
||||
|
||||
use structopt::StructOpt;
|
||||
|
||||
|
||||
@@ -16,14 +16,17 @@
|
||||
|
||||
//! Metered variant of bounded mpsc channels to be able to extract metrics.
|
||||
|
||||
use futures::{channel::mpsc, task::Poll, task::Context, sink::SinkExt, stream::Stream};
|
||||
use futures::{
|
||||
channel::mpsc,
|
||||
sink::SinkExt,
|
||||
stream::Stream,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use std::result;
|
||||
use std::pin::Pin;
|
||||
use std::{pin::Pin, result};
|
||||
|
||||
use super::Meter;
|
||||
|
||||
|
||||
/// Create a wrapped `mpsc::channel` pair of `MeteredSender` and `MeteredReceiver`.
|
||||
pub fn channel<T>(capacity: usize) -> (MeteredSender<T>, MeteredReceiver<T>) {
|
||||
let (tx, rx) = mpsc::channel(capacity);
|
||||
@@ -61,7 +64,7 @@ impl<T> Stream for MeteredReceiver<T> {
|
||||
Poll::Ready(x) => {
|
||||
self.meter.note_received();
|
||||
Poll::Ready(x)
|
||||
}
|
||||
},
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
@@ -84,7 +87,7 @@ impl<T> MeteredReceiver<T> {
|
||||
Some(x) => {
|
||||
self.meter.note_received();
|
||||
Ok(Some(x))
|
||||
}
|
||||
},
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
@@ -96,7 +99,6 @@ impl<T> futures::stream::FusedStream for MeteredReceiver<T> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The sender component, tracking the number of items
|
||||
/// sent across it.
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -16,16 +16,17 @@
|
||||
|
||||
//! Metered variant of mpsc channels to be able to extract metrics.
|
||||
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
};
|
||||
|
||||
use derive_more::{Add, Display};
|
||||
|
||||
mod bounded;
|
||||
mod unbounded;
|
||||
|
||||
pub use self::bounded::*;
|
||||
pub use self::unbounded::*;
|
||||
pub use self::{bounded::*, unbounded::*};
|
||||
|
||||
/// A peek into the inner state of a meter.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
@@ -74,8 +75,7 @@ impl Meter {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use futures::executor::block_on;
|
||||
use futures::StreamExt;
|
||||
use futures::{executor::block_on, StreamExt};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
struct Msg {
|
||||
@@ -137,8 +137,8 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
use std::time::Duration;
|
||||
use futures_timer::Delay;
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn stream_and_sink() {
|
||||
|
||||
@@ -16,14 +16,16 @@
|
||||
|
||||
//! Metered variant of unbounded mpsc channels to be able to extract metrics.
|
||||
|
||||
use futures::{channel::mpsc, task::Poll, task::Context, stream::Stream};
|
||||
use futures::{
|
||||
channel::mpsc,
|
||||
stream::Stream,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use std::result;
|
||||
use std::pin::Pin;
|
||||
use std::{pin::Pin, result};
|
||||
|
||||
use super::Meter;
|
||||
|
||||
|
||||
/// Create a wrapped `mpsc::channel` pair of `MeteredSender` and `MeteredReceiver`.
|
||||
pub fn unbounded<T>() -> (UnboundedMeteredSender<T>, UnboundedMeteredReceiver<T>) {
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
@@ -61,7 +63,7 @@ impl<T> Stream for UnboundedMeteredReceiver<T> {
|
||||
Poll::Ready(x) => {
|
||||
self.meter.note_received();
|
||||
Poll::Ready(x)
|
||||
}
|
||||
},
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
@@ -84,7 +86,7 @@ impl<T> UnboundedMeteredReceiver<T> {
|
||||
Some(x) => {
|
||||
self.meter.note_received();
|
||||
Ok(Some(x))
|
||||
}
|
||||
},
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
@@ -96,7 +98,6 @@ impl<T> futures::stream::FusedStream for UnboundedMeteredReceiver<T> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The sender component, tracking the number of items
|
||||
/// sent across it.
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -28,7 +28,7 @@ use futures::prelude::*;
|
||||
use futures_timer::Delay;
|
||||
use std::{
|
||||
pin::Pin,
|
||||
task::{Poll, Context},
|
||||
task::{Context, Poll},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
@@ -39,7 +39,6 @@ pub mod metrics {
|
||||
/// Reexport Substrate Prometheus types.
|
||||
pub use substrate_prometheus_endpoint as prometheus;
|
||||
|
||||
|
||||
/// Subsystem- or job-specific Prometheus metrics.
|
||||
///
|
||||
/// Usually implemented as a wrapper for `Option<ActualMetrics>`
|
||||
@@ -47,13 +46,17 @@ pub mod metrics {
|
||||
/// Prometheus metrics internally hold an `Arc` reference, so cloning them is fine.
|
||||
pub trait Metrics: Default + Clone {
|
||||
/// Try to register metrics in the Prometheus registry.
|
||||
fn try_register(registry: &prometheus::Registry) -> Result<Self, prometheus::PrometheusError>;
|
||||
fn try_register(
|
||||
registry: &prometheus::Registry,
|
||||
) -> Result<Self, prometheus::PrometheusError>;
|
||||
|
||||
/// Convenience method to register metrics in the optional Prometheus registry.
|
||||
///
|
||||
/// If no registry is provided, returns `Default::default()`. Otherwise, returns the same
|
||||
/// thing that `try_register` does.
|
||||
fn register(registry: Option<&prometheus::Registry>) -> Result<Self, prometheus::PrometheusError> {
|
||||
fn register(
|
||||
registry: Option<&prometheus::Registry>,
|
||||
) -> Result<Self, prometheus::PrometheusError> {
|
||||
match registry {
|
||||
None => Ok(Self::default()),
|
||||
Some(registry) => Self::try_register(registry),
|
||||
@@ -63,7 +66,9 @@ pub mod metrics {
|
||||
|
||||
// dummy impl
|
||||
impl Metrics for () {
|
||||
fn try_register(_registry: &prometheus::Registry) -> Result<(), prometheus::PrometheusError> {
|
||||
fn try_register(
|
||||
_registry: &prometheus::Registry,
|
||||
) -> Result<(), prometheus::PrometheusError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -86,34 +91,27 @@ impl Metronome {
|
||||
/// Create a new metronome source with a defined cycle duration.
|
||||
pub fn new(cycle: Duration) -> Self {
|
||||
let period = cycle.into();
|
||||
Self {
|
||||
period,
|
||||
delay: Delay::new(period),
|
||||
state: MetronomeState::Snooze,
|
||||
}
|
||||
Self { period, delay: Delay::new(period), state: MetronomeState::Snooze }
|
||||
}
|
||||
}
|
||||
|
||||
impl futures::Stream for Metronome {
|
||||
type Item = ();
|
||||
fn poll_next(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>
|
||||
) -> Poll<Option<Self::Item>> {
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
loop {
|
||||
match self.state {
|
||||
MetronomeState::SetAlarm => {
|
||||
let val = self.period.clone();
|
||||
self.delay.reset(val);
|
||||
self.state = MetronomeState::Snooze;
|
||||
}
|
||||
},
|
||||
MetronomeState::Snooze => {
|
||||
if !Pin::new(&mut self.delay).poll(cx).is_ready() {
|
||||
break
|
||||
}
|
||||
self.state = MetronomeState::SetAlarm;
|
||||
return Poll::Ready(Some(()));
|
||||
}
|
||||
return Poll::Ready(Some(()))
|
||||
},
|
||||
}
|
||||
}
|
||||
Poll::Pending
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,17 +14,17 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::time::Duration;
|
||||
use futures::{future, Future, executor};
|
||||
use super::*;
|
||||
use assert_matches::assert_matches;
|
||||
use futures::{executor, future, Future};
|
||||
use polkadot_node_network_protocol::{view, ObservedRole};
|
||||
use polkadot_node_primitives::approval::{
|
||||
AssignmentCertKind, VRFOutput, VRFProof, RELAY_VRF_MODULO_CONTEXT,
|
||||
};
|
||||
use polkadot_node_subsystem::messages::{AllMessages, ApprovalCheckError};
|
||||
use polkadot_node_subsystem_test_helpers as test_helpers;
|
||||
use polkadot_node_subsystem_util::TimeoutExt as _;
|
||||
use polkadot_node_network_protocol::{view, ObservedRole};
|
||||
use polkadot_node_primitives::approval::{
|
||||
AssignmentCertKind, RELAY_VRF_MODULO_CONTEXT, VRFOutput, VRFProof,
|
||||
};
|
||||
use super::*;
|
||||
use std::time::Duration;
|
||||
|
||||
type VirtualOverseer = test_helpers::TestSubsystemContextHandle<ApprovalDistributionMessage>;
|
||||
|
||||
@@ -34,10 +34,7 @@ fn test_harness<T: Future<Output = VirtualOverseer>>(
|
||||
) -> State {
|
||||
let _ = env_logger::builder()
|
||||
.is_test(true)
|
||||
.filter(
|
||||
Some(LOG_TARGET),
|
||||
log::LevelFilter::Trace,
|
||||
)
|
||||
.filter(Some(LOG_TARGET), log::LevelFilter::Trace)
|
||||
.try_init();
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
@@ -52,14 +49,17 @@ fn test_harness<T: Future<Output = VirtualOverseer>>(
|
||||
futures::pin_mut!(test_fut);
|
||||
futures::pin_mut!(subsystem);
|
||||
|
||||
executor::block_on(future::join(async move {
|
||||
let mut overseer = test_fut.await;
|
||||
overseer
|
||||
.send(FromOverseer::Signal(OverseerSignal::Conclude))
|
||||
.timeout(TIMEOUT)
|
||||
.await
|
||||
.expect("Conclude send timeout");
|
||||
}, subsystem));
|
||||
executor::block_on(future::join(
|
||||
async move {
|
||||
let mut overseer = test_fut.await;
|
||||
overseer
|
||||
.send(FromOverseer::Signal(OverseerSignal::Conclude))
|
||||
.timeout(TIMEOUT)
|
||||
.await
|
||||
.expect("Conclude send timeout");
|
||||
},
|
||||
subsystem,
|
||||
));
|
||||
}
|
||||
|
||||
state
|
||||
@@ -67,10 +67,7 @@ fn test_harness<T: Future<Output = VirtualOverseer>>(
|
||||
|
||||
const TIMEOUT: Duration = Duration::from_millis(100);
|
||||
|
||||
async fn overseer_send(
|
||||
overseer: &mut VirtualOverseer,
|
||||
msg: ApprovalDistributionMessage,
|
||||
) {
|
||||
async fn overseer_send(overseer: &mut VirtualOverseer, msg: ApprovalDistributionMessage) {
|
||||
tracing::trace!(msg = ?msg, "Sending message");
|
||||
overseer
|
||||
.send(FromOverseer::Communication { msg })
|
||||
@@ -79,14 +76,8 @@ async fn overseer_send(
|
||||
.expect("msg send timeout");
|
||||
}
|
||||
|
||||
async fn overseer_signal_block_finalized(
|
||||
overseer: &mut VirtualOverseer,
|
||||
number: BlockNumber,
|
||||
) {
|
||||
tracing::trace!(
|
||||
?number,
|
||||
"Sending a finalized signal",
|
||||
);
|
||||
async fn overseer_signal_block_finalized(overseer: &mut VirtualOverseer, number: BlockNumber) {
|
||||
tracing::trace!(?number, "Sending a finalized signal",);
|
||||
// we don't care about the block hash
|
||||
overseer
|
||||
.send(FromOverseer::Signal(OverseerSignal::BlockFinalized(Hash::zero(), number)))
|
||||
@@ -95,15 +86,9 @@ async fn overseer_signal_block_finalized(
|
||||
.expect("signal send timeout");
|
||||
}
|
||||
|
||||
async fn overseer_recv(
|
||||
overseer: &mut VirtualOverseer,
|
||||
) -> AllMessages {
|
||||
async fn overseer_recv(overseer: &mut VirtualOverseer) -> AllMessages {
|
||||
tracing::trace!("Waiting for a message");
|
||||
let msg = overseer
|
||||
.recv()
|
||||
.timeout(TIMEOUT)
|
||||
.await
|
||||
.expect("msg recv timeout");
|
||||
let msg = overseer.recv().timeout(TIMEOUT).await.expect("msg recv timeout");
|
||||
|
||||
tracing::trace!(msg = ?msg, "Received message");
|
||||
|
||||
@@ -117,16 +102,21 @@ async fn setup_peer_with_view(
|
||||
) {
|
||||
overseer_send(
|
||||
virtual_overseer,
|
||||
ApprovalDistributionMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::PeerConnected(peer_id.clone(), ObservedRole::Full, None)
|
||||
)
|
||||
).await;
|
||||
ApprovalDistributionMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerConnected(
|
||||
peer_id.clone(),
|
||||
ObservedRole::Full,
|
||||
None,
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
overseer_send(
|
||||
virtual_overseer,
|
||||
ApprovalDistributionMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::PeerViewChange(peer_id.clone(), view)
|
||||
)
|
||||
).await;
|
||||
ApprovalDistributionMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerViewChange(
|
||||
peer_id.clone(),
|
||||
view,
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn send_message_from_peer(
|
||||
@@ -136,16 +126,15 @@ async fn send_message_from_peer(
|
||||
) {
|
||||
overseer_send(
|
||||
virtual_overseer,
|
||||
ApprovalDistributionMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::PeerMessage(peer_id.clone(), msg)
|
||||
)
|
||||
).await;
|
||||
ApprovalDistributionMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerMessage(
|
||||
peer_id.clone(),
|
||||
msg,
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
fn fake_assignment_cert(
|
||||
block_hash: Hash,
|
||||
validator: ValidatorIndex,
|
||||
) -> IndirectAssignmentCert {
|
||||
fn fake_assignment_cert(block_hash: Hash, validator: ValidatorIndex) -> IndirectAssignmentCert {
|
||||
let ctx = schnorrkel::signing_context(RELAY_VRF_MODULO_CONTEXT);
|
||||
let msg = b"WhenParachains?";
|
||||
let mut prng = rand_core::OsRng;
|
||||
@@ -157,11 +146,9 @@ fn fake_assignment_cert(
|
||||
block_hash,
|
||||
validator,
|
||||
cert: AssignmentCert {
|
||||
kind: AssignmentCertKind::RelayVRFModulo {
|
||||
sample: 1,
|
||||
},
|
||||
kind: AssignmentCertKind::RelayVRFModulo { sample: 1 },
|
||||
vrf: (VRFOutput(out), VRFProof(proof)),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +171,6 @@ async fn expect_reputation_change(
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/// import an assignment
|
||||
/// connect a new peer
|
||||
/// the new peer sends us the same assignment
|
||||
@@ -263,13 +249,7 @@ fn try_import_the_same_assignment() {
|
||||
expect_reputation_change(overseer, &peer_d, COST_UNEXPECTED_MESSAGE).await;
|
||||
expect_reputation_change(overseer, &peer_d, BENEFIT_VALID_MESSAGE).await;
|
||||
|
||||
assert!(overseer
|
||||
.recv()
|
||||
.timeout(TIMEOUT)
|
||||
.await
|
||||
.is_none(),
|
||||
"no message should be sent",
|
||||
);
|
||||
assert!(overseer.recv().timeout(TIMEOUT).await.is_none(), "no message should be sent",);
|
||||
virtual_overseer
|
||||
});
|
||||
}
|
||||
@@ -311,7 +291,8 @@ fn spam_attack_results_in_negative_reputation_change() {
|
||||
let validator_index = ValidatorIndex(candidate_index as u32);
|
||||
let cert = fake_assignment_cert(hash_b, validator_index);
|
||||
(cert, candidate_index as u32)
|
||||
}).collect();
|
||||
})
|
||||
.collect();
|
||||
|
||||
let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments.clone());
|
||||
send_message_from_peer(overseer, peer, msg.clone()).await;
|
||||
@@ -338,10 +319,12 @@ fn spam_attack_results_in_negative_reputation_change() {
|
||||
// send a view update that removes block B from peer's view by bumping the finalized_number
|
||||
overseer_send(
|
||||
overseer,
|
||||
ApprovalDistributionMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::PeerViewChange(peer.clone(), View::with_finalized(2))
|
||||
)
|
||||
).await;
|
||||
ApprovalDistributionMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerViewChange(
|
||||
peer.clone(),
|
||||
View::with_finalized(2),
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
// send the assignments again
|
||||
send_message_from_peer(overseer, peer, msg.clone()).await;
|
||||
@@ -355,7 +338,6 @@ fn spam_attack_results_in_negative_reputation_change() {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// Imagine we send a message to peer A and peer B.
|
||||
/// Upon receiving them, they both will try to send the message each other.
|
||||
/// This test makes sure they will not punish each other for such duplicate messages.
|
||||
@@ -389,16 +371,19 @@ fn peer_sending_us_the_same_we_just_sent_them_is_ok() {
|
||||
let cert = fake_assignment_cert(hash, validator_index);
|
||||
overseer_send(
|
||||
overseer,
|
||||
ApprovalDistributionMessage::DistributeAssignment(cert.clone(), candidate_index)
|
||||
).await;
|
||||
ApprovalDistributionMessage::DistributeAssignment(cert.clone(), candidate_index),
|
||||
)
|
||||
.await;
|
||||
|
||||
// update peer view to include the hash
|
||||
overseer_send(
|
||||
overseer,
|
||||
ApprovalDistributionMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::PeerViewChange(peer.clone(), view![hash])
|
||||
)
|
||||
).await;
|
||||
ApprovalDistributionMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerViewChange(
|
||||
peer.clone(),
|
||||
view![hash],
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
// we should send them the assignment
|
||||
assert_matches!(
|
||||
@@ -420,13 +405,7 @@ fn peer_sending_us_the_same_we_just_sent_them_is_ok() {
|
||||
let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments);
|
||||
send_message_from_peer(overseer, peer, msg.clone()).await;
|
||||
|
||||
assert!(overseer
|
||||
.recv()
|
||||
.timeout(TIMEOUT)
|
||||
.await
|
||||
.is_none(),
|
||||
"we should not punish the peer",
|
||||
);
|
||||
assert!(overseer.recv().timeout(TIMEOUT).await.is_none(), "we should not punish the peer",);
|
||||
|
||||
// send the assignments again
|
||||
send_message_from_peer(overseer, peer, msg).await;
|
||||
@@ -469,8 +448,9 @@ fn import_approval_happy_path() {
|
||||
let cert = fake_assignment_cert(hash, validator_index);
|
||||
overseer_send(
|
||||
overseer,
|
||||
ApprovalDistributionMessage::DistributeAssignment(cert, candidate_index)
|
||||
).await;
|
||||
ApprovalDistributionMessage::DistributeAssignment(cert, candidate_index),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_matches!(
|
||||
overseer_recv(overseer).await,
|
||||
@@ -716,15 +696,9 @@ fn update_peer_view() {
|
||||
let cert_a = fake_assignment_cert(hash_a, ValidatorIndex(0));
|
||||
let cert_b = fake_assignment_cert(hash_b, ValidatorIndex(0));
|
||||
|
||||
overseer_send(
|
||||
overseer,
|
||||
ApprovalDistributionMessage::DistributeAssignment(cert_a, 0)
|
||||
).await;
|
||||
overseer_send(overseer, ApprovalDistributionMessage::DistributeAssignment(cert_a, 0)).await;
|
||||
|
||||
overseer_send(
|
||||
overseer,
|
||||
ApprovalDistributionMessage::DistributeAssignment(cert_b, 0)
|
||||
).await;
|
||||
overseer_send(overseer, ApprovalDistributionMessage::DistributeAssignment(cert_b, 0)).await;
|
||||
|
||||
// connect a peer
|
||||
setup_peer_with_view(overseer, peer, view![hash_a]).await;
|
||||
@@ -747,7 +721,8 @@ fn update_peer_view() {
|
||||
|
||||
assert_eq!(state.peer_views.get(peer).map(|v| v.finalized_number), Some(0));
|
||||
assert_eq!(
|
||||
state.blocks
|
||||
state
|
||||
.blocks
|
||||
.get(&hash_a)
|
||||
.unwrap()
|
||||
.known_by
|
||||
@@ -764,17 +739,20 @@ fn update_peer_view() {
|
||||
// update peer's view
|
||||
overseer_send(
|
||||
overseer,
|
||||
ApprovalDistributionMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::PeerViewChange(peer.clone(), View::new(vec![hash_b, hash_c, hash_d], 2))
|
||||
)
|
||||
).await;
|
||||
ApprovalDistributionMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerViewChange(
|
||||
peer.clone(),
|
||||
View::new(vec![hash_b, hash_c, hash_d], 2),
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
let cert_c = fake_assignment_cert(hash_c, ValidatorIndex(0));
|
||||
|
||||
overseer_send(
|
||||
overseer,
|
||||
ApprovalDistributionMessage::DistributeAssignment(cert_c.clone(), 0)
|
||||
).await;
|
||||
ApprovalDistributionMessage::DistributeAssignment(cert_c.clone(), 0),
|
||||
)
|
||||
.await;
|
||||
|
||||
// we should send relevant assignments to the peer
|
||||
assert_matches!(
|
||||
@@ -795,7 +773,8 @@ fn update_peer_view() {
|
||||
|
||||
assert_eq!(state.peer_views.get(peer).map(|v| v.finalized_number), Some(2));
|
||||
assert_eq!(
|
||||
state.blocks
|
||||
state
|
||||
.blocks
|
||||
.get(&hash_c)
|
||||
.unwrap()
|
||||
.known_by
|
||||
@@ -813,22 +792,17 @@ fn update_peer_view() {
|
||||
// update peer's view
|
||||
overseer_send(
|
||||
overseer,
|
||||
ApprovalDistributionMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::PeerViewChange(peer.clone(), View::with_finalized(finalized_number))
|
||||
)
|
||||
).await;
|
||||
ApprovalDistributionMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerViewChange(
|
||||
peer.clone(),
|
||||
View::with_finalized(finalized_number),
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
virtual_overseer
|
||||
});
|
||||
|
||||
assert_eq!(state.peer_views.get(peer).map(|v| v.finalized_number), Some(finalized_number));
|
||||
assert!(
|
||||
state.blocks
|
||||
.get(&hash_c)
|
||||
.unwrap()
|
||||
.known_by
|
||||
.get(peer)
|
||||
.is_none()
|
||||
);
|
||||
assert!(state.blocks.get(&hash_c).unwrap().known_by.get(peer).is_none());
|
||||
}
|
||||
|
||||
/// E.g. if someone copies the keys...
|
||||
@@ -882,16 +856,11 @@ fn import_remotely_then_locally() {
|
||||
// import the same assignment locally
|
||||
overseer_send(
|
||||
overseer,
|
||||
ApprovalDistributionMessage::DistributeAssignment(cert, candidate_index)
|
||||
).await;
|
||||
ApprovalDistributionMessage::DistributeAssignment(cert, candidate_index),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(overseer
|
||||
.recv()
|
||||
.timeout(TIMEOUT)
|
||||
.await
|
||||
.is_none(),
|
||||
"no message should be sent",
|
||||
);
|
||||
assert!(overseer.recv().timeout(TIMEOUT).await.is_none(), "no message should be sent",);
|
||||
|
||||
// send the approval remotely
|
||||
let approval = IndirectSignedApprovalVote {
|
||||
@@ -916,18 +885,9 @@ fn import_remotely_then_locally() {
|
||||
expect_reputation_change(overseer, peer, BENEFIT_VALID_MESSAGE_FIRST).await;
|
||||
|
||||
// import the same approval locally
|
||||
overseer_send(
|
||||
overseer,
|
||||
ApprovalDistributionMessage::DistributeApproval(approval)
|
||||
).await;
|
||||
overseer_send(overseer, ApprovalDistributionMessage::DistributeApproval(approval)).await;
|
||||
|
||||
assert!(overseer
|
||||
.recv()
|
||||
.timeout(TIMEOUT)
|
||||
.await
|
||||
.is_none(),
|
||||
"no message should be sent",
|
||||
);
|
||||
assert!(overseer.recv().timeout(TIMEOUT).await.is_none(), "no message should be sent",);
|
||||
virtual_overseer
|
||||
});
|
||||
}
|
||||
@@ -967,13 +927,12 @@ fn sends_assignments_even_when_state_is_approved() {
|
||||
|
||||
overseer_send(
|
||||
overseer,
|
||||
ApprovalDistributionMessage::DistributeAssignment(cert.clone(), candidate_index)
|
||||
).await;
|
||||
ApprovalDistributionMessage::DistributeAssignment(cert.clone(), candidate_index),
|
||||
)
|
||||
.await;
|
||||
|
||||
overseer_send(
|
||||
overseer,
|
||||
ApprovalDistributionMessage::DistributeApproval(approval.clone()),
|
||||
).await;
|
||||
overseer_send(overseer, ApprovalDistributionMessage::DistributeApproval(approval.clone()))
|
||||
.await;
|
||||
|
||||
// connect the peer.
|
||||
setup_peer_with_view(overseer, peer, view![hash]).await;
|
||||
@@ -1007,13 +966,7 @@ fn sends_assignments_even_when_state_is_approved() {
|
||||
}
|
||||
);
|
||||
|
||||
assert!(overseer
|
||||
.recv()
|
||||
.timeout(TIMEOUT)
|
||||
.await
|
||||
.is_none(),
|
||||
"no message should be sent",
|
||||
);
|
||||
assert!(overseer.recv().timeout(TIMEOUT).await.is_none(), "no message should be sent",);
|
||||
virtual_overseer
|
||||
});
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ use thiserror::Error;
|
||||
|
||||
use futures::channel::oneshot;
|
||||
|
||||
use polkadot_node_subsystem_util::{Fault, runtime, unwrap_non_fatal};
|
||||
use polkadot_node_subsystem_util::{runtime, unwrap_non_fatal, Fault};
|
||||
use polkadot_subsystem::SubsystemError;
|
||||
|
||||
use crate::LOG_TARGET;
|
||||
@@ -113,9 +113,7 @@ pub type Result<T> = std::result::Result<T, Error>;
|
||||
///
|
||||
/// 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)
|
||||
-> std::result::Result<(), Fatal>
|
||||
{
|
||||
pub fn log_error(result: Result<()>, ctx: &'static str) -> std::result::Result<(), Fatal> {
|
||||
if let Some(error) = unwrap_non_fatal(result.map_err(|e| e.0))? {
|
||||
tracing::warn!(target: LOG_TARGET, error = ?error, ctx);
|
||||
}
|
||||
|
||||
@@ -19,15 +19,13 @@ use futures::{future::Either, FutureExt, StreamExt, TryFutureExt};
|
||||
use sp_keystore::SyncCryptoStorePtr;
|
||||
|
||||
use polkadot_subsystem::{
|
||||
messages::AvailabilityDistributionMessage, FromOverseer, OverseerSignal, SpawnedSubsystem,
|
||||
SubsystemContext, SubsystemError,
|
||||
overseer,
|
||||
messages::AvailabilityDistributionMessage, overseer, FromOverseer, OverseerSignal,
|
||||
SpawnedSubsystem, SubsystemContext, SubsystemError,
|
||||
};
|
||||
|
||||
/// Error and [`Result`] type for this subsystem.
|
||||
mod error;
|
||||
use error::Fatal;
|
||||
use error::{Result, log_error};
|
||||
use error::{log_error, Fatal, Result};
|
||||
|
||||
use polkadot_node_subsystem_util::runtime::RuntimeInfo;
|
||||
|
||||
@@ -70,19 +68,15 @@ where
|
||||
.map_err(|e| SubsystemError::with_origin("availability-distribution", e))
|
||||
.boxed();
|
||||
|
||||
SpawnedSubsystem {
|
||||
name: "availability-distribution-subsystem",
|
||||
future,
|
||||
}
|
||||
SpawnedSubsystem { name: "availability-distribution-subsystem", future }
|
||||
}
|
||||
}
|
||||
|
||||
impl AvailabilityDistributionSubsystem {
|
||||
|
||||
/// Create a new instance of the availability distribution.
|
||||
pub fn new(keystore: SyncCryptoStorePtr, metrics: Metrics) -> Self {
|
||||
let runtime = RuntimeInfo::new(Some(keystore));
|
||||
Self { runtime, metrics }
|
||||
Self { runtime, metrics }
|
||||
}
|
||||
|
||||
/// Start processing work as passed on from the Overseer.
|
||||
@@ -103,44 +97,41 @@ impl AvailabilityDistributionSubsystem {
|
||||
|
||||
// Handle task messages sending:
|
||||
let message = match action {
|
||||
Either::Left(subsystem_msg) => {
|
||||
subsystem_msg.map_err(|e| Fatal::IncomingMessageChannel(e))?
|
||||
}
|
||||
Either::Left(subsystem_msg) =>
|
||||
subsystem_msg.map_err(|e| Fatal::IncomingMessageChannel(e))?,
|
||||
Either::Right(from_task) => {
|
||||
let from_task = from_task.ok_or(Fatal::RequesterExhausted)?;
|
||||
ctx.send_message(from_task).await;
|
||||
continue;
|
||||
}
|
||||
continue
|
||||
},
|
||||
};
|
||||
match message {
|
||||
FromOverseer::Signal(OverseerSignal::ActiveLeaves(update)) => {
|
||||
log_error(
|
||||
requester.get_mut().update_fetching_heads(&mut ctx, &mut self.runtime, update).await,
|
||||
"Error in Requester::update_fetching_heads"
|
||||
requester
|
||||
.get_mut()
|
||||
.update_fetching_heads(&mut ctx, &mut self.runtime, update)
|
||||
.await,
|
||||
"Error in Requester::update_fetching_heads",
|
||||
)?;
|
||||
}
|
||||
FromOverseer::Signal(OverseerSignal::BlockFinalized(..)) => {}
|
||||
FromOverseer::Signal(OverseerSignal::Conclude) => {
|
||||
return Ok(());
|
||||
}
|
||||
},
|
||||
FromOverseer::Signal(OverseerSignal::BlockFinalized(..)) => {},
|
||||
FromOverseer::Signal(OverseerSignal::Conclude) => return Ok(()),
|
||||
FromOverseer::Communication {
|
||||
msg: AvailabilityDistributionMessage::ChunkFetchingRequest(req),
|
||||
} => {
|
||||
answer_chunk_request_log(&mut ctx, req, &self.metrics).await
|
||||
}
|
||||
} => answer_chunk_request_log(&mut ctx, req, &self.metrics).await,
|
||||
FromOverseer::Communication {
|
||||
msg: AvailabilityDistributionMessage::PoVFetchingRequest(req),
|
||||
} => {
|
||||
answer_pov_request_log(&mut ctx, req, &self.metrics).await
|
||||
}
|
||||
} => answer_pov_request_log(&mut ctx, req, &self.metrics).await,
|
||||
FromOverseer::Communication {
|
||||
msg: AvailabilityDistributionMessage::FetchPoV {
|
||||
relay_parent,
|
||||
from_validator,
|
||||
candidate_hash,
|
||||
pov_hash,
|
||||
tx,
|
||||
},
|
||||
msg:
|
||||
AvailabilityDistributionMessage::FetchPoV {
|
||||
relay_parent,
|
||||
from_validator,
|
||||
candidate_hash,
|
||||
pov_hash,
|
||||
tx,
|
||||
},
|
||||
} => {
|
||||
log_error(
|
||||
pov_requester::fetch_pov(
|
||||
@@ -151,10 +142,11 @@ impl AvailabilityDistributionSubsystem {
|
||||
candidate_hash,
|
||||
pov_hash,
|
||||
tx,
|
||||
).await,
|
||||
"pov_requester::fetch_pov"
|
||||
)
|
||||
.await,
|
||||
"pov_requester::fetch_pov",
|
||||
)?;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,13 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use polkadot_node_subsystem_util::metrics::prometheus::{Counter, U64, Registry, PrometheusError, CounterVec, Opts};
|
||||
use polkadot_node_subsystem_util::metrics::prometheus;
|
||||
use polkadot_node_subsystem_util::metrics;
|
||||
use polkadot_node_subsystem_util::{
|
||||
metrics,
|
||||
metrics::{
|
||||
prometheus,
|
||||
prometheus::{Counter, CounterVec, Opts, PrometheusError, Registry, U64},
|
||||
},
|
||||
};
|
||||
|
||||
/// Label for success counters.
|
||||
pub const SUCCEEDED: &'static str = "succeeded";
|
||||
@@ -31,7 +35,6 @@ pub const NOT_FOUND: &'static str = "not-found";
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Metrics(Option<MetricsInner>);
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
struct MetricsInner {
|
||||
/// Number of chunks fetched.
|
||||
@@ -137,4 +140,3 @@ impl metrics::Metrics for Metrics {
|
||||
Ok(Metrics(Some(metrics)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,23 +16,26 @@
|
||||
|
||||
//! PoV requester takes care of requesting PoVs from validators of a backing group.
|
||||
|
||||
use futures::{FutureExt, channel::oneshot, future::BoxFuture};
|
||||
use futures::{channel::oneshot, future::BoxFuture, FutureExt};
|
||||
|
||||
use polkadot_subsystem::jaeger;
|
||||
use polkadot_node_network_protocol::request_response::{OutgoingRequest, Recipient, request::{RequestError, Requests},
|
||||
v1::{PoVFetchingRequest, PoVFetchingResponse}};
|
||||
use polkadot_primitives::v1::{
|
||||
CandidateHash, Hash, ValidatorIndex,
|
||||
use polkadot_node_network_protocol::request_response::{
|
||||
request::{RequestError, Requests},
|
||||
v1::{PoVFetchingRequest, PoVFetchingResponse},
|
||||
OutgoingRequest, Recipient,
|
||||
};
|
||||
use polkadot_node_primitives::PoV;
|
||||
use polkadot_subsystem::{
|
||||
SubsystemContext,
|
||||
messages::{NetworkBridgeMessage, IfDisconnected}
|
||||
};
|
||||
use polkadot_node_subsystem_util::runtime::RuntimeInfo;
|
||||
use polkadot_primitives::v1::{CandidateHash, Hash, ValidatorIndex};
|
||||
use polkadot_subsystem::{
|
||||
jaeger,
|
||||
messages::{IfDisconnected, NetworkBridgeMessage},
|
||||
SubsystemContext,
|
||||
};
|
||||
|
||||
use crate::error::{Fatal, NonFatal};
|
||||
use crate::LOG_TARGET;
|
||||
use crate::{
|
||||
error::{Fatal, NonFatal},
|
||||
LOG_TARGET,
|
||||
};
|
||||
|
||||
/// Start background worker for taking care of fetching the requested `PoV` from the network.
|
||||
pub async fn fetch_pov<Context>(
|
||||
@@ -42,32 +45,31 @@ pub async fn fetch_pov<Context>(
|
||||
from_validator: ValidatorIndex,
|
||||
candidate_hash: CandidateHash,
|
||||
pov_hash: Hash,
|
||||
tx: oneshot::Sender<PoV>
|
||||
tx: oneshot::Sender<PoV>,
|
||||
) -> super::Result<()>
|
||||
where
|
||||
Context: SubsystemContext,
|
||||
{
|
||||
let info = &runtime.get_session_info(ctx.sender(), parent).await?.session_info;
|
||||
let authority_id = info.discovery_keys.get(from_validator.0 as usize)
|
||||
let authority_id = info
|
||||
.discovery_keys
|
||||
.get(from_validator.0 as usize)
|
||||
.ok_or(NonFatal::InvalidValidatorIndex)?
|
||||
.clone();
|
||||
let (req, pending_response) = OutgoingRequest::new(
|
||||
Recipient::Authority(authority_id),
|
||||
PoVFetchingRequest {
|
||||
candidate_hash,
|
||||
},
|
||||
PoVFetchingRequest { candidate_hash },
|
||||
);
|
||||
let full_req = Requests::PoVFetching(req);
|
||||
|
||||
ctx.send_message(
|
||||
NetworkBridgeMessage::SendRequests(
|
||||
vec![full_req],
|
||||
// We are supposed to be connected to validators of our group via `PeerSet`,
|
||||
// but at session boundaries that is kind of racy, in case a connection takes
|
||||
// longer to get established, so we try to connect in any case.
|
||||
IfDisconnected::TryConnect
|
||||
)
|
||||
).await;
|
||||
ctx.send_message(NetworkBridgeMessage::SendRequests(
|
||||
vec![full_req],
|
||||
// We are supposed to be connected to validators of our group via `PeerSet`,
|
||||
// but at session boundaries that is kind of racy, in case a connection takes
|
||||
// longer to get established, so we try to connect in any case.
|
||||
IfDisconnected::TryConnect,
|
||||
))
|
||||
.await;
|
||||
|
||||
let span = jaeger::Span::new(candidate_hash, "fetch-pov")
|
||||
.with_validator_index(from_validator)
|
||||
@@ -85,11 +87,7 @@ async fn fetch_pov_job(
|
||||
tx: oneshot::Sender<PoV>,
|
||||
) {
|
||||
if let Err(err) = do_fetch_pov(pov_hash, pending_response, span, tx).await {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
?err,
|
||||
"fetch_pov_job"
|
||||
);
|
||||
tracing::warn!(target: LOG_TARGET, ?err, "fetch_pov_job");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,15 +97,11 @@ async fn do_fetch_pov(
|
||||
pending_response: BoxFuture<'static, Result<PoVFetchingResponse, RequestError>>,
|
||||
_span: jaeger::Span,
|
||||
tx: oneshot::Sender<PoV>,
|
||||
)
|
||||
-> std::result::Result<(), NonFatal>
|
||||
{
|
||||
) -> std::result::Result<(), NonFatal> {
|
||||
let response = pending_response.await.map_err(NonFatal::FetchPoV)?;
|
||||
let pov = match response {
|
||||
PoVFetchingResponse::PoV(pov) => pov,
|
||||
PoVFetchingResponse::NoSuchPoV => {
|
||||
return Err(NonFatal::NoSuchPoV)
|
||||
}
|
||||
PoVFetchingResponse::NoSuchPoV => return Err(NonFatal::NoSuchPoV),
|
||||
};
|
||||
if pov.hash() == pov_hash {
|
||||
tx.send(pov).map_err(|_| NonFatal::SendResponse)
|
||||
@@ -124,38 +118,37 @@ mod tests {
|
||||
use parity_scale_codec::Encode;
|
||||
use sp_core::testing::TaskExecutor;
|
||||
|
||||
use polkadot_primitives::v1::{CandidateHash, Hash, ValidatorIndex};
|
||||
use polkadot_node_primitives::BlockData;
|
||||
use polkadot_primitives::v1::{CandidateHash, Hash, ValidatorIndex};
|
||||
use polkadot_subsystem::messages::{
|
||||
AllMessages, AvailabilityDistributionMessage, RuntimeApiMessage, RuntimeApiRequest,
|
||||
};
|
||||
use polkadot_subsystem_testhelpers as test_helpers;
|
||||
use polkadot_subsystem::messages::{AvailabilityDistributionMessage, RuntimeApiMessage, RuntimeApiRequest, AllMessages};
|
||||
use test_helpers::mock::make_ferdie_keystore;
|
||||
|
||||
use super::*;
|
||||
use crate::LOG_TARGET;
|
||||
use crate::tests::mock::{make_session_info};
|
||||
use crate::{tests::mock::make_session_info, LOG_TARGET};
|
||||
|
||||
#[test]
|
||||
fn rejects_invalid_pov() {
|
||||
sp_tracing::try_init_simple();
|
||||
let pov = PoV {
|
||||
block_data: BlockData(vec![1,2,3,4,5,6]),
|
||||
};
|
||||
let pov = PoV { block_data: BlockData(vec![1, 2, 3, 4, 5, 6]) };
|
||||
test_run(Hash::default(), pov);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_valid_pov() {
|
||||
sp_tracing::try_init_simple();
|
||||
let pov = PoV {
|
||||
block_data: BlockData(vec![1,2,3,4,5,6]),
|
||||
};
|
||||
let pov = PoV { block_data: BlockData(vec![1, 2, 3, 4, 5, 6]) };
|
||||
test_run(pov.hash(), pov);
|
||||
}
|
||||
|
||||
fn test_run(pov_hash: Hash, pov: PoV) {
|
||||
let pool = TaskExecutor::new();
|
||||
let (mut context, mut virtual_overseer) =
|
||||
test_helpers::make_subsystem_context::<AvailabilityDistributionMessage, TaskExecutor>(pool.clone());
|
||||
let (mut context, mut virtual_overseer) = test_helpers::make_subsystem_context::<
|
||||
AvailabilityDistributionMessage,
|
||||
TaskExecutor,
|
||||
>(pool.clone());
|
||||
let keystore = make_ferdie_keystore();
|
||||
let mut runtime = polkadot_node_subsystem_util::runtime::RuntimeInfo::new(Some(keystore));
|
||||
|
||||
@@ -169,34 +162,33 @@ mod tests {
|
||||
CandidateHash::default(),
|
||||
pov_hash,
|
||||
tx,
|
||||
).await.expect("Should succeed");
|
||||
)
|
||||
.await
|
||||
.expect("Should succeed");
|
||||
};
|
||||
|
||||
let tester = async move {
|
||||
loop {
|
||||
match virtual_overseer.recv().await {
|
||||
AllMessages::RuntimeApi(
|
||||
RuntimeApiMessage::Request(
|
||||
_,
|
||||
RuntimeApiRequest::SessionIndexForChild(tx)
|
||||
)
|
||||
) => {
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
_,
|
||||
RuntimeApiRequest::SessionIndexForChild(tx),
|
||||
)) => {
|
||||
tx.send(Ok(0)).unwrap();
|
||||
}
|
||||
AllMessages::RuntimeApi(
|
||||
RuntimeApiMessage::Request(
|
||||
_,
|
||||
RuntimeApiRequest::SessionInfo(_, tx)
|
||||
)
|
||||
) => {
|
||||
},
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
_,
|
||||
RuntimeApiRequest::SessionInfo(_, tx),
|
||||
)) => {
|
||||
tx.send(Ok(Some(make_session_info()))).unwrap();
|
||||
}
|
||||
},
|
||||
AllMessages::NetworkBridge(NetworkBridgeMessage::SendRequests(mut reqs, _)) => {
|
||||
let req = assert_matches!(
|
||||
reqs.pop(),
|
||||
Some(Requests::PoVFetching(outgoing)) => {outgoing}
|
||||
);
|
||||
req.pending_response.send(Ok(PoVFetchingResponse::PoV(pov.clone()).encode()))
|
||||
req.pending_response
|
||||
.send(Ok(PoVFetchingResponse::PoV(pov.clone()).encode()))
|
||||
.unwrap();
|
||||
break
|
||||
},
|
||||
|
||||
@@ -16,28 +16,33 @@
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use futures::channel::mpsc;
|
||||
use futures::channel::oneshot;
|
||||
use futures::future::select;
|
||||
use futures::{FutureExt, SinkExt};
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
future::select,
|
||||
FutureExt, SinkExt,
|
||||
};
|
||||
|
||||
use polkadot_erasure_coding::branch_hash;
|
||||
use polkadot_node_network_protocol::request_response::{
|
||||
request::{OutgoingRequest, RequestError, Requests, Recipient},
|
||||
request::{OutgoingRequest, Recipient, RequestError, Requests},
|
||||
v1::{ChunkFetchingRequest, ChunkFetchingResponse},
|
||||
};
|
||||
use polkadot_primitives::v1::{AuthorityDiscoveryId, BlakeTwo256, CandidateHash, GroupIndex, Hash, HashT, OccupiedCore, SessionIndex};
|
||||
use polkadot_node_primitives::ErasureChunk;
|
||||
use polkadot_subsystem::messages::{
|
||||
AllMessages, AvailabilityStoreMessage, NetworkBridgeMessage, IfDisconnected,
|
||||
use polkadot_primitives::v1::{
|
||||
AuthorityDiscoveryId, BlakeTwo256, CandidateHash, GroupIndex, Hash, HashT, OccupiedCore,
|
||||
SessionIndex,
|
||||
};
|
||||
use polkadot_subsystem::{
|
||||
jaeger,
|
||||
messages::{AllMessages, AvailabilityStoreMessage, IfDisconnected, NetworkBridgeMessage},
|
||||
SubsystemContext,
|
||||
};
|
||||
use polkadot_subsystem::{SubsystemContext, jaeger};
|
||||
|
||||
use crate::{
|
||||
error::{Fatal, Result},
|
||||
metrics::{Metrics, FAILED, SUCCEEDED},
|
||||
requester::session_cache::{BadValidators, SessionInfo},
|
||||
LOG_TARGET,
|
||||
metrics::{Metrics, SUCCEEDED, FAILED},
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -140,10 +145,7 @@ impl FetchTaskConfig {
|
||||
|
||||
// Don't run tasks for our backing group:
|
||||
if session_info.our_group == Some(core.group_responsible) {
|
||||
return FetchTaskConfig {
|
||||
live_in,
|
||||
prepared_running: None,
|
||||
};
|
||||
return FetchTaskConfig { live_in, prepared_running: None }
|
||||
}
|
||||
|
||||
let span = jaeger::Span::new(core.candidate_hash, "availability-distribution")
|
||||
@@ -165,10 +167,7 @@ impl FetchTaskConfig {
|
||||
sender,
|
||||
span,
|
||||
};
|
||||
FetchTaskConfig {
|
||||
live_in,
|
||||
prepared_running: Some(prepared_running),
|
||||
}
|
||||
FetchTaskConfig { live_in, prepared_running: Some(prepared_running) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,10 +179,7 @@ impl FetchTask {
|
||||
where
|
||||
Context: SubsystemContext,
|
||||
{
|
||||
let FetchTaskConfig {
|
||||
prepared_running,
|
||||
live_in,
|
||||
} = config;
|
||||
let FetchTaskConfig { prepared_running, live_in } = config;
|
||||
|
||||
if let Some(running) = prepared_running {
|
||||
let (handle, kill) = oneshot::channel();
|
||||
@@ -191,15 +187,9 @@ impl FetchTask {
|
||||
ctx.spawn("chunk-fetcher", running.run(kill).boxed())
|
||||
.map_err(|e| Fatal::SpawnTask(e))?;
|
||||
|
||||
Ok(FetchTask {
|
||||
live_in,
|
||||
state: FetchedState::Started(handle),
|
||||
})
|
||||
Ok(FetchTask { live_in, state: FetchedState::Started(handle) })
|
||||
} else {
|
||||
Ok(FetchTask {
|
||||
live_in,
|
||||
state: FetchedState::Canceled,
|
||||
})
|
||||
Ok(FetchTask { live_in, state: FetchedState::Canceled })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,7 +251,9 @@ impl RunningTask {
|
||||
let mut bad_validators = Vec::new();
|
||||
let mut succeeded = false;
|
||||
let mut count: u32 = 0;
|
||||
let mut _span = self.span.child("fetch-task")
|
||||
let mut _span = self
|
||||
.span
|
||||
.child("fetch-task")
|
||||
.with_chunk_index(self.request.index.0)
|
||||
.with_relay_parent(self.relay_parent);
|
||||
// Try validators in reverse order:
|
||||
@@ -271,7 +263,7 @@ impl RunningTask {
|
||||
if count > 0 {
|
||||
self.metrics.on_retry();
|
||||
}
|
||||
count +=1;
|
||||
count += 1;
|
||||
|
||||
// Send request:
|
||||
let resp = match self.do_request(&validator).await {
|
||||
@@ -283,16 +275,14 @@ impl RunningTask {
|
||||
);
|
||||
self.metrics.on_fetch(FAILED);
|
||||
return
|
||||
}
|
||||
},
|
||||
Err(TaskError::PeerError) => {
|
||||
bad_validators.push(validator);
|
||||
continue
|
||||
}
|
||||
},
|
||||
};
|
||||
let chunk = match resp {
|
||||
ChunkFetchingResponse::Chunk(resp) => {
|
||||
resp.recombine_into_chunk(&self.request)
|
||||
}
|
||||
ChunkFetchingResponse::Chunk(resp) => resp.recombine_into_chunk(&self.request),
|
||||
ChunkFetchingResponse::NoSuchChunk => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
@@ -301,20 +291,20 @@ impl RunningTask {
|
||||
);
|
||||
bad_validators.push(validator);
|
||||
continue
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Data genuine?
|
||||
if !self.validate_chunk(&validator, &chunk) {
|
||||
bad_validators.push(validator);
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
|
||||
// Ok, let's store it and be happy:
|
||||
self.store_chunk(chunk).await;
|
||||
succeeded = true;
|
||||
_span.add_string_tag("success", "true");
|
||||
break;
|
||||
break
|
||||
}
|
||||
_span.add_int_tag("tries", count as _);
|
||||
if succeeded {
|
||||
@@ -337,7 +327,7 @@ impl RunningTask {
|
||||
|
||||
self.sender
|
||||
.send(FromFetchTask::Message(AllMessages::NetworkBridge(
|
||||
NetworkBridgeMessage::SendRequests(vec![requests], IfDisconnected::TryConnect)
|
||||
NetworkBridgeMessage::SendRequests(vec![requests], IfDisconnected::TryConnect),
|
||||
)))
|
||||
.await
|
||||
.map_err(|_| TaskError::ShuttingDown)?;
|
||||
@@ -352,7 +342,7 @@ impl RunningTask {
|
||||
"Peer sent us invalid erasure chunk data"
|
||||
);
|
||||
Err(TaskError::PeerError)
|
||||
}
|
||||
},
|
||||
Err(RequestError::NetworkError(err)) => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
@@ -361,13 +351,13 @@ impl RunningTask {
|
||||
"Some network error occurred when fetching erasure chunk"
|
||||
);
|
||||
Err(TaskError::PeerError)
|
||||
}
|
||||
},
|
||||
Err(RequestError::Canceled(oneshot::Canceled)) => {
|
||||
tracing::warn!(target: LOG_TARGET,
|
||||
origin= ?validator,
|
||||
"Erasure chunk request got canceled");
|
||||
Err(TaskError::PeerError)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,13 +373,13 @@ impl RunningTask {
|
||||
error = ?e,
|
||||
"Failed to calculate chunk merkle proof",
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return false
|
||||
},
|
||||
};
|
||||
let erasure_chunk_hash = BlakeTwo256::hash(&chunk.chunk);
|
||||
if anticipated_hash != erasure_chunk_hash {
|
||||
tracing::warn!(target: LOG_TARGET, origin = ?validator, "Received chunk does not match merkle tree");
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -437,12 +427,9 @@ impl RunningTask {
|
||||
}
|
||||
|
||||
async fn conclude_fail(&mut self) {
|
||||
if let Err(err) = self.sender.send(FromFetchTask::Failed(self.request.candidate_hash)).await {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
?err,
|
||||
"Sending `Failed` message for task failed"
|
||||
);
|
||||
if let Err(err) = self.sender.send(FromFetchTask::Failed(self.request.candidate_hash)).await
|
||||
{
|
||||
tracing::warn!(target: LOG_TARGET, ?err, "Sending `Failed` message for task failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,24 +16,24 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
||||
use parity_scale_codec::Encode;
|
||||
|
||||
use futures::channel::{mpsc, oneshot};
|
||||
use futures::{executor, Future, FutureExt, StreamExt, select};
|
||||
use futures::task::{Poll, Context, noop_waker};
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
executor, select,
|
||||
task::{noop_waker, Context, Poll},
|
||||
Future, FutureExt, StreamExt,
|
||||
};
|
||||
|
||||
use sc_network as network;
|
||||
use sp_keyring::Sr25519Keyring;
|
||||
|
||||
use polkadot_primitives::v1::{CandidateHash, ValidatorIndex};
|
||||
use polkadot_node_network_protocol::request_response::{v1, Recipient};
|
||||
use polkadot_node_primitives::{BlockData, PoV};
|
||||
use polkadot_node_network_protocol::request_response::v1;
|
||||
use polkadot_node_network_protocol::request_response::Recipient;
|
||||
use polkadot_primitives::v1::{CandidateHash, ValidatorIndex};
|
||||
|
||||
use crate::metrics::Metrics;
|
||||
use crate::tests::mock::get_valid_chunk_data;
|
||||
use super::*;
|
||||
use crate::{metrics::Metrics, tests::mock::get_valid_chunk_data};
|
||||
|
||||
#[test]
|
||||
fn task_can_be_canceled() {
|
||||
@@ -54,16 +54,14 @@ fn task_does_not_accept_invalid_chunk() {
|
||||
let validators = vec![Sr25519Keyring::Alice.public().into()];
|
||||
task.group = validators;
|
||||
let test = TestRun {
|
||||
chunk_responses: {
|
||||
chunk_responses: {
|
||||
let mut m = HashMap::new();
|
||||
m.insert(
|
||||
Recipient::Authority(Sr25519Keyring::Alice.public().into()),
|
||||
ChunkFetchingResponse::Chunk(
|
||||
v1::ChunkResponse {
|
||||
chunk: vec![1,2,3],
|
||||
proof: vec![vec![9,8,2], vec![2,3,4]],
|
||||
}
|
||||
)
|
||||
ChunkFetchingResponse::Chunk(v1::ChunkResponse {
|
||||
chunk: vec![1, 2, 3],
|
||||
proof: vec![vec![9, 8, 2], vec![2, 3, 4]],
|
||||
}),
|
||||
);
|
||||
m
|
||||
},
|
||||
@@ -75,9 +73,7 @@ fn task_does_not_accept_invalid_chunk() {
|
||||
#[test]
|
||||
fn task_stores_valid_chunk() {
|
||||
let (mut task, rx) = get_test_running_task();
|
||||
let pov = PoV {
|
||||
block_data: BlockData(vec![45, 46, 47]),
|
||||
};
|
||||
let pov = PoV { block_data: BlockData(vec![45, 46, 47]) };
|
||||
let (root_hash, chunk) = get_valid_chunk_data(pov);
|
||||
task.erasure_root = root_hash;
|
||||
task.request.index = chunk.index;
|
||||
@@ -86,16 +82,14 @@ fn task_stores_valid_chunk() {
|
||||
task.group = validators;
|
||||
|
||||
let test = TestRun {
|
||||
chunk_responses: {
|
||||
chunk_responses: {
|
||||
let mut m = HashMap::new();
|
||||
m.insert(
|
||||
Recipient::Authority(Sr25519Keyring::Alice.public().into()),
|
||||
ChunkFetchingResponse::Chunk(
|
||||
v1::ChunkResponse {
|
||||
chunk: chunk.chunk.clone(),
|
||||
proof: chunk.proof,
|
||||
}
|
||||
)
|
||||
ChunkFetchingResponse::Chunk(v1::ChunkResponse {
|
||||
chunk: chunk.chunk.clone(),
|
||||
proof: chunk.proof,
|
||||
}),
|
||||
);
|
||||
m
|
||||
},
|
||||
@@ -111,27 +105,23 @@ fn task_stores_valid_chunk() {
|
||||
#[test]
|
||||
fn task_does_not_accept_wrongly_indexed_chunk() {
|
||||
let (mut task, rx) = get_test_running_task();
|
||||
let pov = PoV {
|
||||
block_data: BlockData(vec![45, 46, 47]),
|
||||
};
|
||||
let pov = PoV { block_data: BlockData(vec![45, 46, 47]) };
|
||||
let (root_hash, chunk) = get_valid_chunk_data(pov);
|
||||
task.erasure_root = root_hash;
|
||||
task.request.index = ValidatorIndex(chunk.index.0+1);
|
||||
task.request.index = ValidatorIndex(chunk.index.0 + 1);
|
||||
|
||||
let validators = vec![Sr25519Keyring::Alice.public().into()];
|
||||
task.group = validators;
|
||||
|
||||
let test = TestRun {
|
||||
chunk_responses: {
|
||||
chunk_responses: {
|
||||
let mut m = HashMap::new();
|
||||
m.insert(
|
||||
Recipient::Authority(Sr25519Keyring::Alice.public().into()),
|
||||
ChunkFetchingResponse::Chunk(
|
||||
v1::ChunkResponse {
|
||||
chunk: chunk.chunk.clone(),
|
||||
proof: chunk.proof,
|
||||
}
|
||||
)
|
||||
ChunkFetchingResponse::Chunk(v1::ChunkResponse {
|
||||
chunk: chunk.chunk.clone(),
|
||||
proof: chunk.proof,
|
||||
}),
|
||||
);
|
||||
m
|
||||
},
|
||||
@@ -144,46 +134,44 @@ fn task_does_not_accept_wrongly_indexed_chunk() {
|
||||
#[test]
|
||||
fn task_stores_valid_chunk_if_there_is_one() {
|
||||
let (mut task, rx) = get_test_running_task();
|
||||
let pov = PoV {
|
||||
block_data: BlockData(vec![45, 46, 47]),
|
||||
};
|
||||
let pov = PoV { block_data: BlockData(vec![45, 46, 47]) };
|
||||
let (root_hash, chunk) = get_valid_chunk_data(pov);
|
||||
task.erasure_root = root_hash;
|
||||
task.request.index = chunk.index;
|
||||
|
||||
let validators = [
|
||||
// Only Alice has valid chunk - should succeed, even though she is tried last.
|
||||
Sr25519Keyring::Alice,
|
||||
Sr25519Keyring::Bob, Sr25519Keyring::Charlie,
|
||||
Sr25519Keyring::Dave, Sr25519Keyring::Eve,
|
||||
]
|
||||
.iter().map(|v| v.public().into()).collect::<Vec<_>>();
|
||||
// Only Alice has valid chunk - should succeed, even though she is tried last.
|
||||
Sr25519Keyring::Alice,
|
||||
Sr25519Keyring::Bob,
|
||||
Sr25519Keyring::Charlie,
|
||||
Sr25519Keyring::Dave,
|
||||
Sr25519Keyring::Eve,
|
||||
]
|
||||
.iter()
|
||||
.map(|v| v.public().into())
|
||||
.collect::<Vec<_>>();
|
||||
task.group = validators;
|
||||
|
||||
let test = TestRun {
|
||||
chunk_responses: {
|
||||
chunk_responses: {
|
||||
let mut m = HashMap::new();
|
||||
m.insert(
|
||||
Recipient::Authority(Sr25519Keyring::Alice.public().into()),
|
||||
ChunkFetchingResponse::Chunk(
|
||||
v1::ChunkResponse {
|
||||
chunk: chunk.chunk.clone(),
|
||||
proof: chunk.proof,
|
||||
}
|
||||
)
|
||||
ChunkFetchingResponse::Chunk(v1::ChunkResponse {
|
||||
chunk: chunk.chunk.clone(),
|
||||
proof: chunk.proof,
|
||||
}),
|
||||
);
|
||||
m.insert(
|
||||
Recipient::Authority(Sr25519Keyring::Bob.public().into()),
|
||||
ChunkFetchingResponse::NoSuchChunk
|
||||
ChunkFetchingResponse::NoSuchChunk,
|
||||
);
|
||||
m.insert(
|
||||
Recipient::Authority(Sr25519Keyring::Charlie.public().into()),
|
||||
ChunkFetchingResponse::Chunk(
|
||||
v1::ChunkResponse {
|
||||
chunk: vec![1,2,3],
|
||||
proof: vec![vec![9,8,2], vec![2,3,4]],
|
||||
}
|
||||
)
|
||||
ChunkFetchingResponse::Chunk(v1::ChunkResponse {
|
||||
chunk: vec![1, 2, 3],
|
||||
proof: vec![vec![9, 8, 2], vec![2, 3, 4]],
|
||||
}),
|
||||
);
|
||||
|
||||
m
|
||||
@@ -205,7 +193,6 @@ struct TestRun {
|
||||
valid_chunks: HashSet<Vec<u8>>,
|
||||
}
|
||||
|
||||
|
||||
impl TestRun {
|
||||
fn run(self, task: RunningTask, rx: mpsc::Receiver<FromFetchTask>) {
|
||||
sp_tracing::try_init_simple();
|
||||
@@ -228,8 +215,7 @@ impl TestRun {
|
||||
match msg {
|
||||
FromFetchTask::Concluded(_) => break,
|
||||
FromFetchTask::Failed(_) => break,
|
||||
FromFetchTask::Message(msg) =>
|
||||
end_ok = self.handle_message(msg).await,
|
||||
FromFetchTask::Message(msg) => end_ok = self.handle_message(msg).await,
|
||||
}
|
||||
}
|
||||
if !end_ok {
|
||||
@@ -242,44 +228,50 @@ impl TestRun {
|
||||
/// end.
|
||||
async fn handle_message(&self, msg: AllMessages) -> bool {
|
||||
match msg {
|
||||
AllMessages::NetworkBridge(NetworkBridgeMessage::SendRequests(reqs, IfDisconnected::TryConnect)) => {
|
||||
AllMessages::NetworkBridge(NetworkBridgeMessage::SendRequests(
|
||||
reqs,
|
||||
IfDisconnected::TryConnect,
|
||||
)) => {
|
||||
let mut valid_responses = 0;
|
||||
for req in reqs {
|
||||
let req = match req {
|
||||
Requests::ChunkFetching(req) => req,
|
||||
_ => panic!("Unexpected request"),
|
||||
};
|
||||
let response = self.chunk_responses.get(&req.peer)
|
||||
.ok_or(network::RequestFailure::Refused);
|
||||
let response =
|
||||
self.chunk_responses.get(&req.peer).ok_or(network::RequestFailure::Refused);
|
||||
|
||||
if let Ok(ChunkFetchingResponse::Chunk(resp)) = &response {
|
||||
if self.valid_chunks.contains(&resp.chunk) {
|
||||
valid_responses += 1;
|
||||
}
|
||||
}
|
||||
req.pending_response.send(response.map(Encode::encode))
|
||||
req.pending_response
|
||||
.send(response.map(Encode::encode))
|
||||
.expect("Sending response should succeed");
|
||||
}
|
||||
return (valid_responses == 0) && self.valid_chunks.is_empty()
|
||||
}
|
||||
AllMessages::AvailabilityStore(
|
||||
AvailabilityStoreMessage::StoreChunk { chunk, tx, .. }
|
||||
) => {
|
||||
},
|
||||
AllMessages::AvailabilityStore(AvailabilityStoreMessage::StoreChunk {
|
||||
chunk,
|
||||
tx,
|
||||
..
|
||||
}) => {
|
||||
assert!(self.valid_chunks.contains(&chunk.chunk));
|
||||
tx.send(Ok(())).expect("Answering fetching task should work");
|
||||
return true
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
tracing::debug!(target: LOG_TARGET, "Unexpected message");
|
||||
return false
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a `RunningTask` filled with dummy values.
|
||||
fn get_test_running_task() -> (RunningTask, mpsc::Receiver<FromFetchTask>) {
|
||||
let (tx,rx) = mpsc::channel(0);
|
||||
let (tx, rx) = mpsc::channel(0);
|
||||
|
||||
(
|
||||
RunningTask {
|
||||
@@ -287,7 +279,7 @@ fn get_test_running_task() -> (RunningTask, mpsc::Receiver<FromFetchTask>) {
|
||||
group_index: GroupIndex(0),
|
||||
group: Vec::new(),
|
||||
request: ChunkFetchingRequest {
|
||||
candidate_hash: CandidateHash([43u8;32].into()),
|
||||
candidate_hash: CandidateHash([43u8; 32].into()),
|
||||
index: ValidatorIndex(0),
|
||||
},
|
||||
erasure_root: Hash::repeat_byte(99),
|
||||
@@ -296,6 +288,6 @@ fn get_test_running_task() -> (RunningTask, mpsc::Receiver<FromFetchTask>) {
|
||||
metrics: Metrics::new_dummy(),
|
||||
span: jaeger::Span::Disabled,
|
||||
},
|
||||
rx
|
||||
rx,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,12 +17,14 @@
|
||||
//! Requester takes care of requesting erasure chunks for candidates that are pending
|
||||
//! availability.
|
||||
|
||||
use std::collections::{
|
||||
hash_map::{Entry, HashMap},
|
||||
hash_set::HashSet,
|
||||
use std::{
|
||||
collections::{
|
||||
hash_map::{Entry, HashMap},
|
||||
hash_set::HashSet,
|
||||
},
|
||||
iter::IntoIterator,
|
||||
pin::Pin,
|
||||
};
|
||||
use std::iter::IntoIterator;
|
||||
use std::pin::Pin;
|
||||
|
||||
use futures::{
|
||||
channel::mpsc,
|
||||
@@ -30,20 +32,18 @@ use futures::{
|
||||
Stream,
|
||||
};
|
||||
|
||||
use polkadot_node_subsystem_util::runtime::{RuntimeInfo, get_occupied_cores};
|
||||
use polkadot_node_subsystem_util::runtime::{get_occupied_cores, RuntimeInfo};
|
||||
use polkadot_primitives::v1::{CandidateHash, Hash, OccupiedCore};
|
||||
use polkadot_subsystem::{
|
||||
messages::AllMessages,
|
||||
ActiveLeavesUpdate, SubsystemContext, ActivatedLeaf,
|
||||
messages::AllMessages, ActivatedLeaf, ActiveLeavesUpdate, SubsystemContext,
|
||||
};
|
||||
|
||||
use super::{LOG_TARGET, Metrics};
|
||||
use super::{Metrics, LOG_TARGET};
|
||||
|
||||
/// Cache for session information.
|
||||
mod session_cache;
|
||||
use session_cache::SessionCache;
|
||||
|
||||
|
||||
/// A task fetching a particular chunk.
|
||||
mod fetch_task;
|
||||
use fetch_task::{FetchTask, FetchTaskConfig, FromFetchTask};
|
||||
@@ -81,13 +81,7 @@ impl Requester {
|
||||
/// by advancing the stream.
|
||||
pub fn new(metrics: Metrics) -> Self {
|
||||
let (tx, rx) = mpsc::channel(1);
|
||||
Requester {
|
||||
fetches: HashMap::new(),
|
||||
session_cache: SessionCache::new(),
|
||||
tx,
|
||||
rx,
|
||||
metrics,
|
||||
}
|
||||
Requester { fetches: HashMap::new(), session_cache: SessionCache::new(), tx, rx, metrics }
|
||||
}
|
||||
/// Update heads that need availability distribution.
|
||||
///
|
||||
@@ -101,15 +95,8 @@ impl Requester {
|
||||
where
|
||||
Context: SubsystemContext,
|
||||
{
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?update,
|
||||
"Update fetching heads"
|
||||
);
|
||||
let ActiveLeavesUpdate {
|
||||
activated,
|
||||
deactivated,
|
||||
} = update;
|
||||
tracing::trace!(target: LOG_TARGET, ?update, "Update fetching heads");
|
||||
let ActiveLeavesUpdate { activated, deactivated } = update;
|
||||
// Order important! We need to handle activated, prior to deactivated, otherwise we might
|
||||
// cancel still needed jobs.
|
||||
self.start_requesting_chunks(ctx, runtime, activated.into_iter()).await?;
|
||||
@@ -194,7 +181,7 @@ impl Requester {
|
||||
e.insert(FetchTask::start(task_cfg, ctx).await?);
|
||||
}
|
||||
// Not a validator, nothing to do.
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -204,28 +191,21 @@ impl Requester {
|
||||
impl Stream for Requester {
|
||||
type Item = AllMessages;
|
||||
|
||||
fn poll_next(
|
||||
mut self: Pin<&mut Self>,
|
||||
ctx: &mut Context,
|
||||
) -> Poll<Option<AllMessages>> {
|
||||
fn poll_next(mut self: Pin<&mut Self>, ctx: &mut Context) -> Poll<Option<AllMessages>> {
|
||||
loop {
|
||||
match Pin::new(&mut self.rx).poll_next(ctx) {
|
||||
Poll::Ready(Some(FromFetchTask::Message(m))) =>
|
||||
return Poll::Ready(Some(m)),
|
||||
Poll::Ready(Some(FromFetchTask::Message(m))) => return Poll::Ready(Some(m)),
|
||||
Poll::Ready(Some(FromFetchTask::Concluded(Some(bad_boys)))) => {
|
||||
self.session_cache.report_bad_log(bad_boys);
|
||||
continue
|
||||
}
|
||||
Poll::Ready(Some(FromFetchTask::Concluded(None))) =>
|
||||
continue,
|
||||
},
|
||||
Poll::Ready(Some(FromFetchTask::Concluded(None))) => continue,
|
||||
Poll::Ready(Some(FromFetchTask::Failed(candidate_hash))) => {
|
||||
// Make sure we retry on next block still pending availability.
|
||||
self.fetches.remove(&candidate_hash);
|
||||
}
|
||||
Poll::Ready(None) =>
|
||||
return Poll::Ready(None),
|
||||
Poll::Pending =>
|
||||
return Poll::Pending,
|
||||
},
|
||||
Poll::Ready(None) => return Poll::Ready(None),
|
||||
Poll::Pending => return Poll::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ use crate::{
|
||||
///
|
||||
/// It should be ensured that a cached session stays live in the cache as long as we might need it.
|
||||
pub struct SessionCache {
|
||||
|
||||
/// Look up cached sessions by `SessionIndex`.
|
||||
///
|
||||
/// Note: Performance of fetching is really secondary here, but we need to ensure we are going
|
||||
@@ -110,12 +109,11 @@ impl SessionCache {
|
||||
|
||||
if let Some(o_info) = self.session_info_cache.get(&session_index) {
|
||||
tracing::trace!(target: LOG_TARGET, session_index, "Got session from lru");
|
||||
return Ok(Some(with_info(o_info)));
|
||||
return Ok(Some(with_info(o_info)))
|
||||
}
|
||||
|
||||
if let Some(info) = self
|
||||
.query_info_from_runtime(ctx, runtime, parent, session_index)
|
||||
.await?
|
||||
if let Some(info) =
|
||||
self.query_info_from_runtime(ctx, runtime, parent, session_index).await?
|
||||
{
|
||||
tracing::trace!(target: LOG_TARGET, session_index, "Calling `with_info`");
|
||||
let r = with_info(&info);
|
||||
@@ -132,7 +130,7 @@ impl SessionCache {
|
||||
/// Not being able to report bad validators is not fatal, so we should not shutdown the
|
||||
/// subsystem on this.
|
||||
pub fn report_bad_log(&mut self, report: BadValidators) {
|
||||
if let Err(err) = self.report_bad(report) {
|
||||
if let Err(err) = self.report_bad(report) {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
err = ?err,
|
||||
@@ -150,10 +148,9 @@ impl SessionCache {
|
||||
.session_info_cache
|
||||
.get_mut(&report.session_index)
|
||||
.ok_or(NonFatal::NoSuchCachedSession)?;
|
||||
let group = session
|
||||
.validator_groups
|
||||
.get_mut(report.group_index.0 as usize)
|
||||
.expect("A bad validator report must contain a valid group for the reported session. qed.");
|
||||
let group = session.validator_groups.get_mut(report.group_index.0 as usize).expect(
|
||||
"A bad validator report must contain a valid group for the reported session. qed.",
|
||||
);
|
||||
let bad_set = report.bad_validators.iter().collect::<HashSet<_>>();
|
||||
|
||||
// Get rid of bad boys:
|
||||
@@ -212,12 +209,7 @@ impl SessionCache {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let info = SessionInfo {
|
||||
validator_groups,
|
||||
our_index,
|
||||
session_index,
|
||||
our_group,
|
||||
};
|
||||
let info = SessionInfo { validator_groups, our_index, session_index, our_group };
|
||||
return Ok(Some(info))
|
||||
}
|
||||
return Ok(None)
|
||||
|
||||
@@ -21,15 +21,15 @@ use std::sync::Arc;
|
||||
use futures::channel::oneshot;
|
||||
|
||||
use polkadot_node_network_protocol::request_response::{request::IncomingRequest, v1};
|
||||
use polkadot_primitives::v1::{CandidateHash, ValidatorIndex};
|
||||
use polkadot_node_primitives::{AvailableData, ErasureChunk};
|
||||
use polkadot_subsystem::{
|
||||
messages::AvailabilityStoreMessage,
|
||||
SubsystemContext, jaeger,
|
||||
};
|
||||
use polkadot_primitives::v1::{CandidateHash, ValidatorIndex};
|
||||
use polkadot_subsystem::{jaeger, messages::AvailabilityStoreMessage, SubsystemContext};
|
||||
|
||||
use crate::error::{NonFatal, Result};
|
||||
use crate::{LOG_TARGET, metrics::{Metrics, SUCCEEDED, FAILED, NOT_FOUND}};
|
||||
use crate::{
|
||||
error::{NonFatal, Result},
|
||||
metrics::{Metrics, FAILED, NOT_FOUND, SUCCEEDED},
|
||||
LOG_TARGET,
|
||||
};
|
||||
|
||||
/// Variant of `answer_pov_request` that does Prometheus metric and logging on errors.
|
||||
///
|
||||
@@ -38,14 +38,12 @@ pub async fn answer_pov_request_log<Context>(
|
||||
ctx: &mut Context,
|
||||
req: IncomingRequest<v1::PoVFetchingRequest>,
|
||||
metrics: &Metrics,
|
||||
)
|
||||
where
|
||||
) where
|
||||
Context: SubsystemContext,
|
||||
{
|
||||
let res = answer_pov_request(ctx, req).await;
|
||||
match res {
|
||||
Ok(result) =>
|
||||
metrics.on_served_pov(if result {SUCCEEDED} else {NOT_FOUND}),
|
||||
Ok(result) => metrics.on_served_pov(if result { SUCCEEDED } else { NOT_FOUND }),
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
@@ -53,7 +51,7 @@ where
|
||||
"Serving PoV failed with error"
|
||||
);
|
||||
metrics.on_served_pov(FAILED);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,8 +68,7 @@ where
|
||||
{
|
||||
let res = answer_chunk_request(ctx, req).await;
|
||||
match res {
|
||||
Ok(result) =>
|
||||
metrics.on_served_chunk(if result {SUCCEEDED} else {NOT_FOUND}),
|
||||
Ok(result) => metrics.on_served_chunk(if result { SUCCEEDED } else { NOT_FOUND }),
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
@@ -79,7 +76,7 @@ where
|
||||
"Serving chunk failed with error"
|
||||
);
|
||||
metrics.on_served_chunk(FAILED);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +101,7 @@ where
|
||||
Some(av_data) => {
|
||||
let pov = Arc::try_unwrap(av_data.pov).unwrap_or_else(|a| (&*a).clone());
|
||||
v1::PoVFetchingResponse::PoV(pov)
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
req.send_response(response).map_err(|_| NonFatal::SendResponse)?;
|
||||
@@ -123,8 +120,7 @@ where
|
||||
{
|
||||
let span = jaeger::Span::new(req.payload.candidate_hash, "answer-chunk-request");
|
||||
|
||||
let _child_span = span.child("answer-chunk-request")
|
||||
.with_chunk_index(req.payload.index.0);
|
||||
let _child_span = span.child("answer-chunk-request").with_chunk_index(req.payload.index.0);
|
||||
|
||||
let chunk = query_chunk(ctx, req.payload.candidate_hash, req.payload.index).await?;
|
||||
|
||||
@@ -158,10 +154,8 @@ where
|
||||
Context: SubsystemContext,
|
||||
{
|
||||
let (tx, rx) = oneshot::channel();
|
||||
ctx.send_message(
|
||||
AvailabilityStoreMessage::QueryChunk(candidate_hash, validator_index, tx),
|
||||
)
|
||||
.await;
|
||||
ctx.send_message(AvailabilityStoreMessage::QueryChunk(candidate_hash, validator_index, tx))
|
||||
.await;
|
||||
|
||||
let result = rx.await.map_err(|e| {
|
||||
tracing::trace!(
|
||||
@@ -185,10 +179,8 @@ where
|
||||
Context: SubsystemContext,
|
||||
{
|
||||
let (tx, rx) = oneshot::channel();
|
||||
ctx.send_message(
|
||||
AvailabilityStoreMessage::QueryAvailableData(candidate_hash, tx),
|
||||
)
|
||||
.await;
|
||||
ctx.send_message(AvailabilityStoreMessage::QueryAvailableData(candidate_hash, tx))
|
||||
.await;
|
||||
|
||||
let result = rx.await.map_err(|e| NonFatal::QueryAvailableDataResponseChannel(e))?;
|
||||
Ok(result)
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
//! Helper functions and tools to generate mock data useful for testing this subsystem.
|
||||
|
||||
use std::sync::Arc;
|
||||
@@ -22,43 +21,44 @@ use std::sync::Arc;
|
||||
use sp_keyring::Sr25519Keyring;
|
||||
|
||||
use polkadot_erasure_coding::{branches, obtain_chunks_v1 as obtain_chunks};
|
||||
use polkadot_node_primitives::{AvailableData, BlockData, ErasureChunk, PoV};
|
||||
use polkadot_primitives::v1::{
|
||||
CandidateCommitments, CandidateDescriptor, CandidateHash,
|
||||
CommittedCandidateReceipt, GroupIndex, Hash, HeadData, Id as ParaId,
|
||||
OccupiedCore, PersistedValidationData, SessionInfo, ValidatorIndex
|
||||
CandidateCommitments, CandidateDescriptor, CandidateHash, CommittedCandidateReceipt,
|
||||
GroupIndex, Hash, HeadData, Id as ParaId, OccupiedCore, PersistedValidationData, SessionInfo,
|
||||
ValidatorIndex,
|
||||
};
|
||||
use polkadot_node_primitives::{PoV, ErasureChunk, AvailableData, BlockData};
|
||||
|
||||
|
||||
/// Create dummy session info with two validator groups.
|
||||
pub fn make_session_info() -> SessionInfo {
|
||||
let validators = vec![
|
||||
Sr25519Keyring::Ferdie, // <- this node, role: validator
|
||||
Sr25519Keyring::Alice,
|
||||
Sr25519Keyring::Bob,
|
||||
Sr25519Keyring::Charlie,
|
||||
Sr25519Keyring::Dave,
|
||||
Sr25519Keyring::Eve,
|
||||
Sr25519Keyring::One,
|
||||
];
|
||||
let validators = vec![
|
||||
Sr25519Keyring::Ferdie, // <- this node, role: validator
|
||||
Sr25519Keyring::Alice,
|
||||
Sr25519Keyring::Bob,
|
||||
Sr25519Keyring::Charlie,
|
||||
Sr25519Keyring::Dave,
|
||||
Sr25519Keyring::Eve,
|
||||
Sr25519Keyring::One,
|
||||
];
|
||||
|
||||
let validator_groups: Vec<Vec<ValidatorIndex>> = [vec![5, 0, 3], vec![1, 6, 2, 4]]
|
||||
.iter().map(|g| g.into_iter().map(|v| ValidatorIndex(*v)).collect()).collect();
|
||||
let validator_groups: Vec<Vec<ValidatorIndex>> = [vec![5, 0, 3], vec![1, 6, 2, 4]]
|
||||
.iter()
|
||||
.map(|g| g.into_iter().map(|v| ValidatorIndex(*v)).collect())
|
||||
.collect();
|
||||
|
||||
SessionInfo {
|
||||
discovery_keys: validators.iter().map(|k| k.public().into()).collect(),
|
||||
// Not used:
|
||||
n_cores: validator_groups.len() as u32,
|
||||
validator_groups,
|
||||
// Not used values:
|
||||
validators: validators.iter().map(|k| k.public().into()).collect(),
|
||||
assignment_keys: Vec::new(),
|
||||
zeroth_delay_tranche_width: 0,
|
||||
relay_vrf_modulo_samples: 0,
|
||||
n_delay_tranches: 0,
|
||||
no_show_slots: 0,
|
||||
needed_approvals: 0,
|
||||
}
|
||||
SessionInfo {
|
||||
discovery_keys: validators.iter().map(|k| k.public().into()).collect(),
|
||||
// Not used:
|
||||
n_cores: validator_groups.len() as u32,
|
||||
validator_groups,
|
||||
// Not used values:
|
||||
validators: validators.iter().map(|k| k.public().into()).collect(),
|
||||
assignment_keys: Vec::new(),
|
||||
zeroth_delay_tranche_width: 0,
|
||||
relay_vrf_modulo_samples: 0,
|
||||
n_delay_tranches: 0,
|
||||
no_show_slots: 0,
|
||||
needed_approvals: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for constructing occupied cores.
|
||||
@@ -72,9 +72,7 @@ pub struct OccupiedCoreBuilder {
|
||||
|
||||
impl OccupiedCoreBuilder {
|
||||
pub fn build(self) -> (OccupiedCore, (CandidateHash, ErasureChunk)) {
|
||||
let pov = PoV {
|
||||
block_data: BlockData(vec![45, 46, 47]),
|
||||
};
|
||||
let pov = PoV { block_data: BlockData(vec![45, 46, 47]) };
|
||||
let pov_hash = pov.hash();
|
||||
let (erasure_root, chunk) = get_valid_chunk_data(pov.clone());
|
||||
let candidate_receipt = TestCandidateBuilder {
|
||||
@@ -83,7 +81,8 @@ impl OccupiedCoreBuilder {
|
||||
relay_parent: self.relay_parent,
|
||||
erasure_root,
|
||||
..Default::default()
|
||||
}.build();
|
||||
}
|
||||
.build();
|
||||
let core = OccupiedCore {
|
||||
next_up_on_available: None,
|
||||
occupied_since: 0,
|
||||
@@ -117,10 +116,7 @@ impl TestCandidateBuilder {
|
||||
erasure_root: self.erasure_root,
|
||||
..Default::default()
|
||||
},
|
||||
commitments: CandidateCommitments {
|
||||
head_data: self.head_data,
|
||||
..Default::default()
|
||||
},
|
||||
commitments: CandidateCommitments { head_data: self.head_data, ..Default::default() },
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,18 +130,18 @@ pub fn get_valid_chunk_data(pov: PoV) -> (Hash, ErasureChunk) {
|
||||
max_pov_size: 1024,
|
||||
relay_parent_storage_root: Default::default(),
|
||||
};
|
||||
let available_data = AvailableData {
|
||||
validation_data: persisted, pov: Arc::new(pov),
|
||||
};
|
||||
let available_data = AvailableData { validation_data: persisted, pov: Arc::new(pov) };
|
||||
let chunks = obtain_chunks(fake_validator_count, &available_data).unwrap();
|
||||
let branches = branches(chunks.as_ref());
|
||||
let root = branches.root();
|
||||
let chunk = branches.enumerate()
|
||||
.map(|(index, (proof, chunk))| ErasureChunk {
|
||||
chunk: chunk.to_vec(),
|
||||
index: ValidatorIndex(index as _),
|
||||
proof,
|
||||
})
|
||||
.next().expect("There really should be 10 chunks.");
|
||||
let chunk = branches
|
||||
.enumerate()
|
||||
.map(|(index, (proof, chunk))| ErasureChunk {
|
||||
chunk: chunk.to_vec(),
|
||||
index: ValidatorIndex(index as _),
|
||||
proof,
|
||||
})
|
||||
.next()
|
||||
.expect("There really should be 10 chunks.");
|
||||
(root, chunk)
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ use super::*;
|
||||
|
||||
mod state;
|
||||
/// State for test harnesses.
|
||||
use state::{TestState, TestHarness};
|
||||
use state::{TestHarness, TestState};
|
||||
|
||||
/// Mock data useful for testing.
|
||||
pub(crate) mod mock;
|
||||
@@ -60,9 +60,7 @@ fn test_harness<T: Future<Output = ()>>(
|
||||
#[test]
|
||||
fn check_basic() {
|
||||
let state = TestState::default();
|
||||
test_harness(state.keystore.clone(), move |harness| {
|
||||
state.run(harness)
|
||||
});
|
||||
test_harness(state.keystore.clone(), move |harness| state.run(harness));
|
||||
}
|
||||
|
||||
/// Check whether requester tries all validators in group.
|
||||
@@ -75,9 +73,7 @@ fn check_fetch_tries_all() {
|
||||
v.push(None);
|
||||
v.push(None);
|
||||
}
|
||||
test_harness(state.keystore.clone(), move |harness| {
|
||||
state.run(harness)
|
||||
});
|
||||
test_harness(state.keystore.clone(), move |harness| state.run(harness));
|
||||
}
|
||||
|
||||
/// Check whether requester tries all validators in group
|
||||
@@ -87,10 +83,9 @@ fn check_fetch_tries_all() {
|
||||
#[test]
|
||||
fn check_fetch_retry() {
|
||||
let mut state = TestState::default();
|
||||
state.cores.insert(
|
||||
state.relay_chain[2],
|
||||
state.cores.get(&state.relay_chain[1]).unwrap().clone(),
|
||||
);
|
||||
state
|
||||
.cores
|
||||
.insert(state.relay_chain[2], state.cores.get(&state.relay_chain[1]).unwrap().clone());
|
||||
// We only care about the first three blocks.
|
||||
// 1. scheduled
|
||||
// 2. occupied
|
||||
@@ -98,20 +93,18 @@ fn check_fetch_retry() {
|
||||
state.relay_chain.truncate(3);
|
||||
|
||||
// Get rid of unused valid chunks:
|
||||
let valid_candidate_hashes: HashSet<_> = state.cores
|
||||
let valid_candidate_hashes: HashSet<_> = state
|
||||
.cores
|
||||
.get(&state.relay_chain[1])
|
||||
.iter()
|
||||
.flat_map(|v| v.iter())
|
||||
.filter_map(|c| {
|
||||
match c {
|
||||
CoreState::Occupied(core) => Some(core.candidate_hash),
|
||||
_ => None,
|
||||
}
|
||||
.filter_map(|c| match c {
|
||||
CoreState::Occupied(core) => Some(core.candidate_hash),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
state.valid_chunks.retain(|(ch, _)| valid_candidate_hashes.contains(ch));
|
||||
|
||||
|
||||
for (_, v) in state.chunks.iter_mut() {
|
||||
// This should still succeed as cores are still pending availability on next block.
|
||||
v.push(None);
|
||||
@@ -120,7 +113,5 @@ fn check_fetch_retry() {
|
||||
v.push(None);
|
||||
v.push(None);
|
||||
}
|
||||
test_harness(state.keystore.clone(), move |harness| {
|
||||
state.run(harness)
|
||||
});
|
||||
test_harness(state.keystore.clone(), move |harness| state.run(harness));
|
||||
}
|
||||
|
||||
@@ -14,38 +14,44 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{collections::{HashMap, HashSet}, sync::Arc, time::Duration};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use polkadot_node_subsystem_util::TimeoutExt;
|
||||
use polkadot_subsystem_testhelpers::TestSubsystemContextHandle;
|
||||
|
||||
use futures::{FutureExt, channel::oneshot, SinkExt, channel::mpsc, StreamExt};
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
FutureExt, SinkExt, StreamExt,
|
||||
};
|
||||
use futures_timer::Delay;
|
||||
|
||||
use sp_keystore::SyncCryptoStorePtr;
|
||||
use sp_core::{traits::SpawnNamed, testing::TaskExecutor};
|
||||
use sc_network as network;
|
||||
use sc_network::IfDisconnected;
|
||||
use sc_network::config as netconfig;
|
||||
use sc_network::{config as netconfig, IfDisconnected};
|
||||
use sp_core::{testing::TaskExecutor, traits::SpawnNamed};
|
||||
use sp_keystore::SyncCryptoStorePtr;
|
||||
|
||||
use polkadot_subsystem::{
|
||||
ActiveLeavesUpdate, FromOverseer, OverseerSignal, ActivatedLeaf, LeafStatus,
|
||||
messages::{
|
||||
AllMessages, AvailabilityDistributionMessage, AvailabilityStoreMessage, NetworkBridgeMessage,
|
||||
RuntimeApiMessage, RuntimeApiRequest,
|
||||
}
|
||||
};
|
||||
use polkadot_primitives::v1::{CandidateHash, CoreState, GroupIndex, Hash, Id
|
||||
as ParaId, ScheduledCore, SessionInfo,
|
||||
ValidatorIndex
|
||||
};
|
||||
use polkadot_node_primitives::ErasureChunk;
|
||||
use polkadot_node_network_protocol::{
|
||||
jaeger,
|
||||
request_response::{IncomingRequest, OutgoingRequest, Requests, v1}
|
||||
request_response::{v1, IncomingRequest, OutgoingRequest, Requests},
|
||||
};
|
||||
use polkadot_node_primitives::ErasureChunk;
|
||||
use polkadot_primitives::v1::{
|
||||
CandidateHash, CoreState, GroupIndex, Hash, Id as ParaId, ScheduledCore, SessionInfo,
|
||||
ValidatorIndex,
|
||||
};
|
||||
use polkadot_subsystem::{
|
||||
messages::{
|
||||
AllMessages, AvailabilityDistributionMessage, AvailabilityStoreMessage,
|
||||
NetworkBridgeMessage, RuntimeApiMessage, RuntimeApiRequest,
|
||||
},
|
||||
ActivatedLeaf, ActiveLeavesUpdate, FromOverseer, LeafStatus, OverseerSignal,
|
||||
};
|
||||
use polkadot_subsystem_testhelpers as test_helpers;
|
||||
use test_helpers::{SingleItemSink, mock::make_ferdie_keystore};
|
||||
use test_helpers::{mock::make_ferdie_keystore, SingleItemSink};
|
||||
|
||||
use super::mock::{make_session_info, OccupiedCoreBuilder};
|
||||
use crate::LOG_TARGET;
|
||||
@@ -94,35 +100,32 @@ impl Default for TestState {
|
||||
let mut cores = HashMap::new();
|
||||
let mut chunks = HashMap::new();
|
||||
|
||||
cores.insert(relay_chain[0],
|
||||
cores.insert(
|
||||
relay_chain[0],
|
||||
vec![
|
||||
CoreState::Scheduled(ScheduledCore {
|
||||
para_id: chain_ids[0],
|
||||
collator: None,
|
||||
}),
|
||||
CoreState::Scheduled(ScheduledCore {
|
||||
para_id: chain_ids[1],
|
||||
collator: None,
|
||||
}),
|
||||
]
|
||||
CoreState::Scheduled(ScheduledCore { para_id: chain_ids[0], collator: None }),
|
||||
CoreState::Scheduled(ScheduledCore { para_id: chain_ids[1], collator: None }),
|
||||
],
|
||||
);
|
||||
|
||||
let heads = {
|
||||
let heads = {
|
||||
let mut advanced = relay_chain.iter();
|
||||
advanced.next();
|
||||
relay_chain.iter().zip(advanced)
|
||||
};
|
||||
for (relay_parent, relay_child) in heads {
|
||||
let (p_cores, p_chunks): (Vec<_>, Vec<_>) = chain_ids.iter().enumerate()
|
||||
let (p_cores, p_chunks): (Vec<_>, Vec<_>) = chain_ids
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, para_id)| {
|
||||
let (core, chunk) = OccupiedCoreBuilder {
|
||||
group_responsible: GroupIndex(i as _),
|
||||
para_id: *para_id,
|
||||
relay_parent: relay_parent.clone(),
|
||||
}.build();
|
||||
}
|
||||
.build();
|
||||
(CoreState::Occupied(core), chunk)
|
||||
}
|
||||
)
|
||||
})
|
||||
.unzip();
|
||||
cores.insert(relay_child.clone(), p_cores);
|
||||
// Skip chunks for our own group (won't get fetched):
|
||||
@@ -146,11 +149,12 @@ impl Default for TestState {
|
||||
}
|
||||
|
||||
impl TestState {
|
||||
|
||||
/// Run, but fail after some timeout.
|
||||
pub async fn run(self, harness: TestHarness) {
|
||||
// Make sure test won't run forever.
|
||||
let f = self.run_inner(harness.pool, harness.virtual_overseer).timeout(Duration::from_secs(10));
|
||||
let f = self
|
||||
.run_inner(harness.pool, harness.virtual_overseer)
|
||||
.timeout(Duration::from_secs(10));
|
||||
assert!(f.await.is_some(), "Test ran into timeout");
|
||||
}
|
||||
|
||||
@@ -167,17 +171,19 @@ impl TestState {
|
||||
let updates = {
|
||||
let mut advanced = self.relay_chain.iter();
|
||||
advanced.next();
|
||||
self
|
||||
.relay_chain.iter().zip(advanced)
|
||||
.map(|(old, new)| ActiveLeavesUpdate {
|
||||
activated: Some(ActivatedLeaf {
|
||||
hash: new.clone(),
|
||||
number: 1,
|
||||
status: LeafStatus::Fresh,
|
||||
span: Arc::new(jaeger::Span::Disabled),
|
||||
}),
|
||||
deactivated: vec![old.clone()].into(),
|
||||
}).collect::<Vec<_>>()
|
||||
self.relay_chain
|
||||
.iter()
|
||||
.zip(advanced)
|
||||
.map(|(old, new)| ActiveLeavesUpdate {
|
||||
activated: Some(ActivatedLeaf {
|
||||
hash: new.clone(),
|
||||
number: 1,
|
||||
status: LeafStatus::Fresh,
|
||||
span: Arc::new(jaeger::Span::Disabled),
|
||||
}),
|
||||
deactivated: vec![old.clone()].into(),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
// We should be storing all valid chunks during execution:
|
||||
@@ -190,24 +196,27 @@ impl TestState {
|
||||
// Spawning necessary as incoming queue can only hold a single item, we don't want to dead
|
||||
// lock ;-)
|
||||
let update_tx = tx.clone();
|
||||
executor.spawn("Sending active leaves updates", async move {
|
||||
for update in updates {
|
||||
overseer_signal(
|
||||
update_tx.clone(),
|
||||
OverseerSignal::ActiveLeaves(update)
|
||||
).await;
|
||||
// We need to give the subsystem a little time to do its job, otherwise it will
|
||||
// cancel jobs as obsolete:
|
||||
Delay::new(Duration::from_millis(20)).await;
|
||||
executor.spawn(
|
||||
"Sending active leaves updates",
|
||||
async move {
|
||||
for update in updates {
|
||||
overseer_signal(update_tx.clone(), OverseerSignal::ActiveLeaves(update)).await;
|
||||
// We need to give the subsystem a little time to do its job, otherwise it will
|
||||
// cancel jobs as obsolete:
|
||||
Delay::new(Duration::from_millis(20)).await;
|
||||
}
|
||||
}
|
||||
}.boxed());
|
||||
.boxed(),
|
||||
);
|
||||
|
||||
while remaining_stores > 0
|
||||
{
|
||||
while remaining_stores > 0 {
|
||||
tracing::trace!(target: LOG_TARGET, remaining_stores, "Stores left to go");
|
||||
let msg = overseer_recv(&mut rx).await;
|
||||
match msg {
|
||||
AllMessages::NetworkBridge(NetworkBridgeMessage::SendRequests(reqs, IfDisconnected::TryConnect)) => {
|
||||
AllMessages::NetworkBridge(NetworkBridgeMessage::SendRequests(
|
||||
reqs,
|
||||
IfDisconnected::TryConnect,
|
||||
)) => {
|
||||
for req in reqs {
|
||||
// Forward requests:
|
||||
let in_req = to_incoming_req(&executor, req);
|
||||
@@ -215,50 +224,61 @@ impl TestState {
|
||||
executor.spawn(
|
||||
"Request forwarding",
|
||||
overseer_send(
|
||||
tx.clone(),
|
||||
AvailabilityDistributionMessage::ChunkFetchingRequest(in_req)
|
||||
).boxed()
|
||||
tx.clone(),
|
||||
AvailabilityDistributionMessage::ChunkFetchingRequest(in_req),
|
||||
)
|
||||
.boxed(),
|
||||
);
|
||||
}
|
||||
}
|
||||
AllMessages::AvailabilityStore(AvailabilityStoreMessage::QueryChunk(candidate_hash, validator_index, tx)) => {
|
||||
let chunk = self.chunks.get_mut(&(candidate_hash, validator_index)).map(Vec::pop).flatten().flatten();
|
||||
tx.send(chunk)
|
||||
.expect("Receiver is expected to be alive");
|
||||
}
|
||||
AllMessages::AvailabilityStore(AvailabilityStoreMessage::StoreChunk{candidate_hash, chunk, tx, ..}) => {
|
||||
},
|
||||
AllMessages::AvailabilityStore(AvailabilityStoreMessage::QueryChunk(
|
||||
candidate_hash,
|
||||
validator_index,
|
||||
tx,
|
||||
)) => {
|
||||
let chunk = self
|
||||
.chunks
|
||||
.get_mut(&(candidate_hash, validator_index))
|
||||
.map(Vec::pop)
|
||||
.flatten()
|
||||
.flatten();
|
||||
tx.send(chunk).expect("Receiver is expected to be alive");
|
||||
},
|
||||
AllMessages::AvailabilityStore(AvailabilityStoreMessage::StoreChunk {
|
||||
candidate_hash,
|
||||
chunk,
|
||||
tx,
|
||||
..
|
||||
}) => {
|
||||
assert!(
|
||||
self.valid_chunks.contains(&(candidate_hash, chunk.index)),
|
||||
"Only valid chunks should ever get stored."
|
||||
);
|
||||
tx.send(Ok(()))
|
||||
.expect("Receiver is expected to be alive");
|
||||
tx.send(Ok(())).expect("Receiver is expected to be alive");
|
||||
tracing::trace!(target: LOG_TARGET, "'Stored' fetched chunk.");
|
||||
remaining_stores -= 1;
|
||||
}
|
||||
},
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(hash, req)) => {
|
||||
match req {
|
||||
RuntimeApiRequest::SessionIndexForChild(tx) => {
|
||||
// Always session index 1 for now:
|
||||
tx.send(Ok(1))
|
||||
.expect("Receiver should still be alive");
|
||||
}
|
||||
tx.send(Ok(1)).expect("Receiver should still be alive");
|
||||
},
|
||||
RuntimeApiRequest::SessionInfo(_, tx) => {
|
||||
tx.send(Ok(Some(self.session_info.clone())))
|
||||
.expect("Receiver should be alive.");
|
||||
}
|
||||
.expect("Receiver should be alive.");
|
||||
},
|
||||
RuntimeApiRequest::AvailabilityCores(tx) => {
|
||||
tracing::trace!(target: LOG_TARGET, cores= ?self.cores[&hash], hash = ?hash, "Sending out cores for hash");
|
||||
tx.send(Ok(self.cores[&hash].clone()))
|
||||
.expect("Receiver should still be alive");
|
||||
}
|
||||
.expect("Receiver should still be alive");
|
||||
},
|
||||
_ => {
|
||||
panic!("Unexpected runtime request: {:?}", req);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,9 +292,7 @@ async fn overseer_signal(
|
||||
) {
|
||||
let msg = msg.into();
|
||||
tracing::trace!(target: LOG_TARGET, msg = ?msg, "sending message");
|
||||
tx.send(FromOverseer::Signal(msg))
|
||||
.await
|
||||
.expect("Test subsystem no longer live");
|
||||
tx.send(FromOverseer::Signal(msg)).await.expect("Test subsystem no longer live");
|
||||
}
|
||||
|
||||
async fn overseer_send(
|
||||
@@ -283,42 +301,44 @@ async fn overseer_send(
|
||||
) {
|
||||
let msg = msg.into();
|
||||
tracing::trace!(target: LOG_TARGET, msg = ?msg, "sending message");
|
||||
tx.send(FromOverseer::Communication { msg }).await
|
||||
tx.send(FromOverseer::Communication { msg })
|
||||
.await
|
||||
.expect("Test subsystem no longer live");
|
||||
tracing::trace!(target: LOG_TARGET, "sent message");
|
||||
}
|
||||
|
||||
|
||||
async fn overseer_recv(
|
||||
rx: &mut mpsc::UnboundedReceiver<AllMessages>,
|
||||
) -> AllMessages {
|
||||
async fn overseer_recv(rx: &mut mpsc::UnboundedReceiver<AllMessages>) -> AllMessages {
|
||||
tracing::trace!(target: LOG_TARGET, "waiting for message ...");
|
||||
rx.next().await.expect("Test subsystem no longer live")
|
||||
}
|
||||
|
||||
fn to_incoming_req(
|
||||
executor: &TaskExecutor,
|
||||
outgoing: Requests
|
||||
outgoing: Requests,
|
||||
) -> IncomingRequest<v1::ChunkFetchingRequest> {
|
||||
match outgoing {
|
||||
Requests::ChunkFetching(OutgoingRequest { payload, pending_response, .. }) => {
|
||||
let (tx, rx): (oneshot::Sender<netconfig::OutgoingResponse>, oneshot::Receiver<_>)
|
||||
= oneshot::channel();
|
||||
executor.spawn("Message forwarding", async {
|
||||
let response = rx.await;
|
||||
let payload = response.expect("Unexpected canceled request").result;
|
||||
pending_response.send(payload.map_err(|_| network::RequestFailure::Refused))
|
||||
.expect("Sending response is expected to work");
|
||||
}.boxed()
|
||||
let (tx, rx): (oneshot::Sender<netconfig::OutgoingResponse>, oneshot::Receiver<_>) =
|
||||
oneshot::channel();
|
||||
executor.spawn(
|
||||
"Message forwarding",
|
||||
async {
|
||||
let response = rx.await;
|
||||
let payload = response.expect("Unexpected canceled request").result;
|
||||
pending_response
|
||||
.send(payload.map_err(|_| network::RequestFailure::Refused))
|
||||
.expect("Sending response is expected to work");
|
||||
}
|
||||
.boxed(),
|
||||
);
|
||||
|
||||
IncomingRequest::new(
|
||||
// We don't really care:
|
||||
network::PeerId::random(),
|
||||
payload,
|
||||
tx
|
||||
tx,
|
||||
)
|
||||
}
|
||||
},
|
||||
_ => panic!("Unexpected request!"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,40 +18,42 @@
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::pin::Pin;
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
pin::Pin,
|
||||
};
|
||||
|
||||
use futures::{channel::oneshot, prelude::*, stream::FuturesUnordered};
|
||||
use futures::future::{BoxFuture, RemoteHandle, FutureExt};
|
||||
use futures::task::{Context, Poll};
|
||||
use futures::{
|
||||
channel::oneshot,
|
||||
future::{BoxFuture, FutureExt, RemoteHandle},
|
||||
prelude::*,
|
||||
stream::FuturesUnordered,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use lru::LruCache;
|
||||
use rand::seq::SliceRandom;
|
||||
|
||||
use polkadot_primitives::v1::{
|
||||
AuthorityDiscoveryId, CandidateReceipt, CandidateHash,
|
||||
Hash, ValidatorId, ValidatorIndex,
|
||||
SessionInfo, SessionIndex, BlakeTwo256, HashT, GroupIndex, BlockNumber,
|
||||
use polkadot_erasure_coding::{branch_hash, branches, obtain_chunks_v1, recovery_threshold};
|
||||
use polkadot_node_network_protocol::{
|
||||
request_response::{
|
||||
self as req_res, request::RequestError, OutgoingRequest, Recipient, Requests,
|
||||
},
|
||||
IfDisconnected,
|
||||
};
|
||||
use polkadot_node_primitives::{AvailableData, ErasureChunk};
|
||||
use polkadot_node_subsystem_util::request_session_info;
|
||||
use polkadot_primitives::v1::{
|
||||
AuthorityDiscoveryId, BlakeTwo256, BlockNumber, CandidateHash, CandidateReceipt, GroupIndex,
|
||||
Hash, HashT, SessionIndex, SessionInfo, ValidatorId, ValidatorIndex,
|
||||
};
|
||||
use polkadot_node_primitives::{ErasureChunk, AvailableData};
|
||||
use polkadot_subsystem::{
|
||||
overseer::{self, Subsystem},
|
||||
SubsystemContext, SubsystemResult, SubsystemError, SpawnedSubsystem, FromOverseer,
|
||||
OverseerSignal, ActiveLeavesUpdate, SubsystemSender,
|
||||
errors::RecoveryError,
|
||||
jaeger,
|
||||
messages::{
|
||||
AvailabilityStoreMessage, AvailabilityRecoveryMessage, NetworkBridgeMessage,
|
||||
},
|
||||
messages::{AvailabilityRecoveryMessage, AvailabilityStoreMessage, NetworkBridgeMessage},
|
||||
overseer::{self, Subsystem},
|
||||
ActiveLeavesUpdate, FromOverseer, OverseerSignal, SpawnedSubsystem, SubsystemContext,
|
||||
SubsystemError, SubsystemResult, SubsystemSender,
|
||||
};
|
||||
use polkadot_node_network_protocol::{
|
||||
IfDisconnected,
|
||||
request_response::{
|
||||
self as req_res, OutgoingRequest, Recipient, Requests,
|
||||
request::RequestError,
|
||||
},
|
||||
};
|
||||
use polkadot_node_subsystem_util::request_session_info;
|
||||
use polkadot_erasure_coding::{branches, branch_hash, recovery_threshold, obtain_chunks_v1};
|
||||
|
||||
mod error;
|
||||
|
||||
@@ -82,9 +84,8 @@ struct RequestChunksPhase {
|
||||
// request the chunk from them.
|
||||
shuffling: VecDeque<ValidatorIndex>,
|
||||
received_chunks: HashMap<ValidatorIndex, ErasureChunk>,
|
||||
requesting_chunks: FuturesUnordered<BoxFuture<
|
||||
'static,
|
||||
Result<Option<ErasureChunk>, (ValidatorIndex, RequestError)>>,
|
||||
requesting_chunks: FuturesUnordered<
|
||||
BoxFuture<'static, Result<Option<ErasureChunk>, (ValidatorIndex, RequestError)>>,
|
||||
>,
|
||||
}
|
||||
|
||||
@@ -125,9 +126,7 @@ impl RequestFromBackersPhase {
|
||||
fn new(mut backers: Vec<ValidatorIndex>) -> Self {
|
||||
backers.shuffle(&mut rand::thread_rng());
|
||||
|
||||
RequestFromBackersPhase {
|
||||
shuffled_backers: backers,
|
||||
}
|
||||
RequestFromBackersPhase { shuffled_backers: backers }
|
||||
}
|
||||
|
||||
// Run this phase to completion.
|
||||
@@ -144,29 +143,41 @@ impl RequestFromBackersPhase {
|
||||
);
|
||||
loop {
|
||||
// Pop the next backer, and proceed to next phase if we're out.
|
||||
let validator_index = self.shuffled_backers.pop().ok_or_else(|| RecoveryError::Unavailable)?;
|
||||
let validator_index =
|
||||
self.shuffled_backers.pop().ok_or_else(|| RecoveryError::Unavailable)?;
|
||||
|
||||
// Request data.
|
||||
let (req, res) = OutgoingRequest::new(
|
||||
Recipient::Authority(params.validator_authority_keys[validator_index.0 as usize].clone()),
|
||||
Recipient::Authority(
|
||||
params.validator_authority_keys[validator_index.0 as usize].clone(),
|
||||
),
|
||||
req_res::v1::AvailableDataFetchingRequest { candidate_hash: params.candidate_hash },
|
||||
);
|
||||
|
||||
sender.send_message(NetworkBridgeMessage::SendRequests(
|
||||
vec![Requests::AvailableDataFetching(req)],
|
||||
IfDisconnected::TryConnect,
|
||||
).into()).await;
|
||||
sender
|
||||
.send_message(
|
||||
NetworkBridgeMessage::SendRequests(
|
||||
vec![Requests::AvailableDataFetching(req)],
|
||||
IfDisconnected::TryConnect,
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
.await;
|
||||
|
||||
match res.await {
|
||||
Ok(req_res::v1::AvailableDataFetchingResponse::AvailableData(data)) => {
|
||||
if reconstructed_data_matches_root(params.validators.len(), ¶ms.erasure_root, &data) {
|
||||
if reconstructed_data_matches_root(
|
||||
params.validators.len(),
|
||||
¶ms.erasure_root,
|
||||
&data,
|
||||
) {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
candidate_hash = ?params.candidate_hash,
|
||||
"Received full data",
|
||||
);
|
||||
|
||||
return Ok(data);
|
||||
return Ok(data)
|
||||
} else {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
@@ -177,8 +188,8 @@ impl RequestFromBackersPhase {
|
||||
|
||||
// it doesn't help to report the peer with req/res.
|
||||
}
|
||||
}
|
||||
Ok(req_res::v1::AvailableDataFetchingResponse::NoSuchData) => {}
|
||||
},
|
||||
Ok(req_res::v1::AvailableDataFetchingResponse::NoSuchData) => {},
|
||||
Err(e) => tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
candidate_hash = ?params.candidate_hash,
|
||||
@@ -239,34 +250,34 @@ impl RequestChunksPhase {
|
||||
index: validator_index,
|
||||
};
|
||||
|
||||
let (req, res) = OutgoingRequest::new(
|
||||
Recipient::Authority(validator),
|
||||
raw_request.clone(),
|
||||
);
|
||||
let (req, res) =
|
||||
OutgoingRequest::new(Recipient::Authority(validator), raw_request.clone());
|
||||
|
||||
sender.send_message(NetworkBridgeMessage::SendRequests(
|
||||
vec![Requests::ChunkFetching(req)],
|
||||
IfDisconnected::TryConnect,
|
||||
).into()).await;
|
||||
sender
|
||||
.send_message(
|
||||
NetworkBridgeMessage::SendRequests(
|
||||
vec![Requests::ChunkFetching(req)],
|
||||
IfDisconnected::TryConnect,
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
.await;
|
||||
|
||||
self.requesting_chunks.push(Box::pin(async move {
|
||||
match res.await {
|
||||
Ok(req_res::v1::ChunkFetchingResponse::Chunk(chunk))
|
||||
=> Ok(Some(chunk.recombine_into_chunk(&raw_request))),
|
||||
Ok(req_res::v1::ChunkFetchingResponse::Chunk(chunk)) =>
|
||||
Ok(Some(chunk.recombine_into_chunk(&raw_request))),
|
||||
Ok(req_res::v1::ChunkFetchingResponse::NoSuchChunk) => Ok(None),
|
||||
Err(e) => Err((validator_index, e)),
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
break;
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_for_chunks(
|
||||
&mut self,
|
||||
params: &InteractionParams,
|
||||
) {
|
||||
async fn wait_for_chunks(&mut self, params: &InteractionParams) {
|
||||
// Wait for all current requests to conclude or time-out, or until we reach enough chunks.
|
||||
while let Some(request_result) = self.requesting_chunks.next().await {
|
||||
match request_result {
|
||||
@@ -275,11 +286,9 @@ impl RequestChunksPhase {
|
||||
|
||||
let validator_index = chunk.index;
|
||||
|
||||
if let Ok(anticipated_hash) = branch_hash(
|
||||
¶ms.erasure_root,
|
||||
&chunk.proof,
|
||||
chunk.index.0 as usize,
|
||||
) {
|
||||
if let Ok(anticipated_hash) =
|
||||
branch_hash(¶ms.erasure_root, &chunk.proof, chunk.index.0 as usize)
|
||||
{
|
||||
let erasure_chunk_hash = BlakeTwo256::hash(&chunk.chunk);
|
||||
|
||||
if erasure_chunk_hash != anticipated_hash {
|
||||
@@ -303,8 +312,8 @@ impl RequestChunksPhase {
|
||||
"Invalid Merkle proof",
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(None) => {}
|
||||
},
|
||||
Ok(None) => {},
|
||||
Err((validator_index, e)) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
@@ -314,17 +323,19 @@ impl RequestChunksPhase {
|
||||
);
|
||||
|
||||
match e {
|
||||
RequestError::InvalidResponse(_) => {}
|
||||
RequestError::InvalidResponse(_) => {},
|
||||
RequestError::NetworkError(_) | RequestError::Canceled(_) => {
|
||||
self.shuffling.push_front(validator_index);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Stop waiting for requests when we either can already recover the data
|
||||
// or have gotten firm 'No' responses from enough validators.
|
||||
if self.can_conclude(params) { break }
|
||||
if self.can_conclude(params) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,9 +347,11 @@ impl RequestChunksPhase {
|
||||
// First query the store for any chunks we've got.
|
||||
{
|
||||
let (tx, rx) = oneshot::channel();
|
||||
sender.send_message(
|
||||
AvailabilityStoreMessage::QueryAllChunks(params.candidate_hash, tx).into()
|
||||
).await;
|
||||
sender
|
||||
.send_message(
|
||||
AvailabilityStoreMessage::QueryAllChunks(params.candidate_hash, tx).into(),
|
||||
)
|
||||
.await;
|
||||
|
||||
match rx.await {
|
||||
Ok(chunks) => {
|
||||
@@ -350,14 +363,14 @@ impl RequestChunksPhase {
|
||||
for chunk in chunks {
|
||||
self.received_chunks.insert(chunk.index, chunk);
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(oneshot::Canceled) => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
candidate_hash = ?params.candidate_hash,
|
||||
"Failed to reach the availability store"
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,7 +386,7 @@ impl RequestChunksPhase {
|
||||
"Data recovery is not possible",
|
||||
);
|
||||
|
||||
return Err(RecoveryError::Unavailable);
|
||||
return Err(RecoveryError::Unavailable)
|
||||
}
|
||||
|
||||
self.launch_parallel_requests(params, sender).await;
|
||||
@@ -388,7 +401,11 @@ impl RequestChunksPhase {
|
||||
self.received_chunks.values().map(|c| (&c.chunk[..], c.index.0 as usize)),
|
||||
) {
|
||||
Ok(data) => {
|
||||
if reconstructed_data_matches_root(params.validators.len(), ¶ms.erasure_root, &data) {
|
||||
if reconstructed_data_matches_root(
|
||||
params.validators.len(),
|
||||
¶ms.erasure_root,
|
||||
&data,
|
||||
) {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
candidate_hash = ?params.candidate_hash,
|
||||
@@ -407,7 +424,7 @@ impl RequestChunksPhase {
|
||||
|
||||
Err(RecoveryError::Invalid)
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
@@ -419,7 +436,7 @@ impl RequestChunksPhase {
|
||||
|
||||
Err(RecoveryError::Invalid)
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -447,8 +464,8 @@ fn reconstructed_data_matches_root(
|
||||
err = ?e,
|
||||
"Failed to obtain chunks",
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return false
|
||||
},
|
||||
};
|
||||
|
||||
let branches = branches(&chunks);
|
||||
@@ -461,20 +478,23 @@ impl<S: SubsystemSender> Interaction<S> {
|
||||
// First just see if we have the data available locally.
|
||||
{
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.sender.send_message(
|
||||
AvailabilityStoreMessage::QueryAvailableData(self.params.candidate_hash, tx).into()
|
||||
).await;
|
||||
self.sender
|
||||
.send_message(
|
||||
AvailabilityStoreMessage::QueryAvailableData(self.params.candidate_hash, tx)
|
||||
.into(),
|
||||
)
|
||||
.await;
|
||||
|
||||
match rx.await {
|
||||
Ok(Some(data)) => return Ok(data),
|
||||
Ok(None) => {}
|
||||
Ok(None) => {},
|
||||
Err(oneshot::Canceled) => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
candidate_hash = ?self.params.candidate_hash,
|
||||
"Failed to reach the availability store",
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,16 +506,14 @@ impl<S: SubsystemSender> Interaction<S> {
|
||||
match from_backers.run(&self.params, &mut self.sender).await {
|
||||
Ok(data) => break Ok(data),
|
||||
Err(RecoveryError::Invalid) => break Err(RecoveryError::Invalid),
|
||||
Err(RecoveryError::Unavailable) => {
|
||||
self.phase = InteractionPhase::RequestChunks(
|
||||
RequestChunksPhase::new(self.params.validators.len() as _)
|
||||
)
|
||||
}
|
||||
Err(RecoveryError::Unavailable) =>
|
||||
self.phase = InteractionPhase::RequestChunks(RequestChunksPhase::new(
|
||||
self.params.validators.len() as _,
|
||||
)),
|
||||
}
|
||||
}
|
||||
InteractionPhase::RequestChunks(ref mut from_all) => {
|
||||
break from_all.run(&self.params, &mut self.sender).await;
|
||||
}
|
||||
},
|
||||
InteractionPhase::RequestChunks(ref mut from_all) =>
|
||||
break from_all.run(&self.params, &mut self.sender).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -514,7 +532,7 @@ impl Future for InteractionHandle {
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let mut indices_to_remove = Vec::new();
|
||||
for (i, awaiting) in self.awaiting.iter_mut().enumerate().rev() {
|
||||
if let Poll::Ready(()) = awaiting.poll_canceled(cx) {
|
||||
if let Poll::Ready(()) = awaiting.poll_canceled(cx) {
|
||||
indices_to_remove.push(i);
|
||||
}
|
||||
}
|
||||
@@ -537,7 +555,7 @@ impl Future for InteractionHandle {
|
||||
"All receivers for available data dropped.",
|
||||
);
|
||||
|
||||
return Poll::Ready(None);
|
||||
return Poll::Ready(None)
|
||||
}
|
||||
|
||||
let remote = &mut self.remote;
|
||||
@@ -580,21 +598,16 @@ where
|
||||
Context: overseer::SubsystemContext<Message = AvailabilityRecoveryMessage>,
|
||||
{
|
||||
fn start(self, ctx: Context) -> SpawnedSubsystem {
|
||||
let future = self.run(ctx)
|
||||
let future = self
|
||||
.run(ctx)
|
||||
.map_err(|e| SubsystemError::with_origin("availability-recovery", e))
|
||||
.boxed();
|
||||
SpawnedSubsystem {
|
||||
name: "availability-recovery-subsystem",
|
||||
future,
|
||||
}
|
||||
SpawnedSubsystem { name: "availability-recovery-subsystem", future }
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles a signal from the overseer.
|
||||
async fn handle_signal(
|
||||
state: &mut State,
|
||||
signal: OverseerSignal,
|
||||
) -> SubsystemResult<bool> {
|
||||
async fn handle_signal(state: &mut State, signal: OverseerSignal) -> SubsystemResult<bool> {
|
||||
match signal {
|
||||
OverseerSignal::Conclude => Ok(true),
|
||||
OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { activated, .. }) => {
|
||||
@@ -606,8 +619,8 @@ async fn handle_signal(
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
OverseerSignal::BlockFinalized(_, _) => Ok(false)
|
||||
},
|
||||
OverseerSignal::BlockFinalized(_, _) => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -636,18 +649,14 @@ where
|
||||
|
||||
let phase = backing_group
|
||||
.and_then(|g| session_info.validator_groups.get(g.0 as usize))
|
||||
.map(|group| InteractionPhase::RequestFromBackers(
|
||||
RequestFromBackersPhase::new(group.clone())
|
||||
))
|
||||
.unwrap_or_else(|| InteractionPhase::RequestChunks(
|
||||
RequestChunksPhase::new(params.validators.len() as _)
|
||||
));
|
||||
.map(|group| {
|
||||
InteractionPhase::RequestFromBackers(RequestFromBackersPhase::new(group.clone()))
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
InteractionPhase::RequestChunks(RequestChunksPhase::new(params.validators.len() as _))
|
||||
});
|
||||
|
||||
let interaction = Interaction {
|
||||
sender: ctx.sender().clone(),
|
||||
params,
|
||||
phase,
|
||||
};
|
||||
let interaction = Interaction { sender: ctx.sender().clone(), params, phase };
|
||||
|
||||
let (remote, remote_handle) = interaction.run().remote_handle();
|
||||
|
||||
@@ -694,43 +703,32 @@ where
|
||||
"Error responding with an availability recovery result",
|
||||
);
|
||||
}
|
||||
return Ok(());
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
if let Some(i) = state.interactions.iter_mut().find(|i| i.candidate_hash == candidate_hash) {
|
||||
i.awaiting.push(response_sender);
|
||||
return Ok(());
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let _span = span.child("not-cached");
|
||||
let session_info = request_session_info(
|
||||
state.live_block.1,
|
||||
session_index,
|
||||
ctx.sender(),
|
||||
).await.await.map_err(error::Error::CanceledSessionInfo)??;
|
||||
let session_info = request_session_info(state.live_block.1, session_index, ctx.sender())
|
||||
.await
|
||||
.await
|
||||
.map_err(error::Error::CanceledSessionInfo)??;
|
||||
|
||||
let _span = span.child("session-info-ctx-received");
|
||||
match session_info {
|
||||
Some(session_info) => {
|
||||
launch_interaction(
|
||||
state,
|
||||
ctx,
|
||||
session_info,
|
||||
receipt,
|
||||
backing_group,
|
||||
response_sender,
|
||||
).await
|
||||
}
|
||||
Some(session_info) =>
|
||||
launch_interaction(state, ctx, session_info, receipt, backing_group, response_sender)
|
||||
.await,
|
||||
None => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
"SessionInfo is `None` at {:?}", state.live_block,
|
||||
);
|
||||
tracing::warn!(target: LOG_TARGET, "SessionInfo is `None` at {:?}", state.live_block,);
|
||||
response_sender
|
||||
.send(Err(RecoveryError::Unavailable))
|
||||
.map_err(|_| error::Error::CanceledResponseSender)?;
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -744,9 +742,8 @@ where
|
||||
Context: overseer::SubsystemContext<Message = AvailabilityRecoveryMessage>,
|
||||
{
|
||||
let (tx, rx) = oneshot::channel();
|
||||
ctx.send_message(
|
||||
AvailabilityStoreMessage::QueryAvailableData(candidate_hash, tx),
|
||||
).await;
|
||||
ctx.send_message(AvailabilityStoreMessage::QueryAvailableData(candidate_hash, tx))
|
||||
.await;
|
||||
|
||||
Ok(rx.await.map_err(error::Error::CanceledQueryFullData)?)
|
||||
}
|
||||
@@ -762,10 +759,7 @@ impl AvailabilityRecoverySubsystem {
|
||||
Self { fast_path: false }
|
||||
}
|
||||
|
||||
async fn run<Context>(
|
||||
self,
|
||||
mut ctx: Context,
|
||||
) -> SubsystemResult<()>
|
||||
async fn run<Context>(self, mut ctx: Context) -> SubsystemResult<()>
|
||||
where
|
||||
Context: SubsystemContext<Message = AvailabilityRecoveryMessage>,
|
||||
Context: overseer::SubsystemContext<Message = AvailabilityRecoveryMessage>,
|
||||
|
||||
@@ -14,27 +14,26 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::time::Duration;
|
||||
use std::sync::Arc;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use assert_matches::assert_matches;
|
||||
use futures::{executor, future};
|
||||
use futures_timer::Delay;
|
||||
use assert_matches::assert_matches;
|
||||
|
||||
use parity_scale_codec::Encode;
|
||||
|
||||
use super::*;
|
||||
|
||||
use polkadot_primitives::v1::{
|
||||
AuthorityDiscoveryId, PersistedValidationData, HeadData,
|
||||
};
|
||||
use polkadot_node_primitives::{PoV, BlockData};
|
||||
use polkadot_erasure_coding::{branches, obtain_chunks_v1 as obtain_chunks};
|
||||
use polkadot_node_primitives::{BlockData, PoV};
|
||||
use polkadot_node_subsystem_util::TimeoutExt;
|
||||
use polkadot_subsystem_testhelpers as test_helpers;
|
||||
use polkadot_primitives::v1::{AuthorityDiscoveryId, HeadData, PersistedValidationData};
|
||||
use polkadot_subsystem::{
|
||||
messages::{AllMessages, RuntimeApiMessage, RuntimeApiRequest}, jaeger, ActivatedLeaf, LeafStatus,
|
||||
jaeger,
|
||||
messages::{AllMessages, RuntimeApiMessage, RuntimeApiRequest},
|
||||
ActivatedLeaf, LeafStatus,
|
||||
};
|
||||
use polkadot_subsystem_testhelpers as test_helpers;
|
||||
|
||||
type VirtualOverseer = test_helpers::TestSubsystemContextHandle<AvailabilityRecoveryMessage>;
|
||||
|
||||
@@ -43,10 +42,7 @@ fn test_harness_fast_path<T: Future<Output = VirtualOverseer>>(
|
||||
) {
|
||||
let _ = env_logger::builder()
|
||||
.is_test(true)
|
||||
.filter(
|
||||
Some("polkadot_availability_recovery"),
|
||||
log::LevelFilter::Trace,
|
||||
)
|
||||
.filter(Some("polkadot_availability_recovery"), log::LevelFilter::Trace)
|
||||
.try_init();
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
@@ -61,10 +57,15 @@ fn test_harness_fast_path<T: Future<Output = VirtualOverseer>>(
|
||||
futures::pin_mut!(test_fut);
|
||||
futures::pin_mut!(subsystem);
|
||||
|
||||
executor::block_on(future::join(async move {
|
||||
let mut overseer = test_fut.await;
|
||||
overseer_signal(&mut overseer, OverseerSignal::Conclude).await;
|
||||
}, subsystem)).1.unwrap();
|
||||
executor::block_on(future::join(
|
||||
async move {
|
||||
let mut overseer = test_fut.await;
|
||||
overseer_signal(&mut overseer, OverseerSignal::Conclude).await;
|
||||
},
|
||||
subsystem,
|
||||
))
|
||||
.1
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn test_harness_chunks_only<T: Future<Output = VirtualOverseer>>(
|
||||
@@ -72,10 +73,7 @@ fn test_harness_chunks_only<T: Future<Output = VirtualOverseer>>(
|
||||
) {
|
||||
let _ = env_logger::builder()
|
||||
.is_test(true)
|
||||
.filter(
|
||||
Some("polkadot_availability_recovery"),
|
||||
log::LevelFilter::Trace,
|
||||
)
|
||||
.filter(Some("polkadot_availability_recovery"), log::LevelFilter::Trace)
|
||||
.try_init();
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
@@ -90,10 +88,15 @@ fn test_harness_chunks_only<T: Future<Output = VirtualOverseer>>(
|
||||
futures::pin_mut!(test_fut);
|
||||
futures::pin_mut!(subsystem);
|
||||
|
||||
executor::block_on(future::join(async move {
|
||||
let mut overseer = test_fut.await;
|
||||
overseer_signal(&mut overseer, OverseerSignal::Conclude).await;
|
||||
}, subsystem)).1.unwrap();
|
||||
executor::block_on(future::join(
|
||||
async move {
|
||||
let mut overseer = test_fut.await;
|
||||
overseer_signal(&mut overseer, OverseerSignal::Conclude).await;
|
||||
},
|
||||
subsystem,
|
||||
))
|
||||
.1
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
const TIMEOUT: Duration = Duration::from_millis(100);
|
||||
@@ -132,16 +135,11 @@ async fn overseer_recv(
|
||||
overseer: &mut test_helpers::TestSubsystemContextHandle<AvailabilityRecoveryMessage>,
|
||||
) -> AllMessages {
|
||||
tracing::trace!("waiting for message ...");
|
||||
let msg = overseer
|
||||
.recv()
|
||||
.timeout(TIMEOUT)
|
||||
.await
|
||||
.expect("TIMEOUT is enough to recv.");
|
||||
let msg = overseer.recv().timeout(TIMEOUT).await.expect("TIMEOUT is enough to recv.");
|
||||
tracing::trace!(msg = ?msg, "received message");
|
||||
msg
|
||||
}
|
||||
|
||||
|
||||
use sp_keyring::Sr25519Keyring;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -153,9 +151,7 @@ enum Has {
|
||||
|
||||
impl Has {
|
||||
fn timeout() -> Self {
|
||||
Has::NetworkError(sc_network::RequestFailure::Network(
|
||||
sc_network::OutboundFailure::Timeout
|
||||
))
|
||||
Has::NetworkError(sc_network::RequestFailure::Network(sc_network::OutboundFailure::Timeout))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,10 +179,7 @@ impl TestState {
|
||||
self.validators.len() - self.threshold() + 1
|
||||
}
|
||||
|
||||
async fn test_runtime_api(
|
||||
&self,
|
||||
virtual_overseer: &mut VirtualOverseer,
|
||||
) {
|
||||
async fn test_runtime_api(&self, virtual_overseer: &mut VirtualOverseer) {
|
||||
assert_matches!(
|
||||
overseer_recv(virtual_overseer).await,
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
@@ -233,7 +226,7 @@ impl TestState {
|
||||
async fn respond_to_query_all_request(
|
||||
&self,
|
||||
virtual_overseer: &mut VirtualOverseer,
|
||||
send_chunk: impl Fn(usize) -> bool
|
||||
send_chunk: impl Fn(usize) -> bool,
|
||||
) {
|
||||
assert_matches!(
|
||||
overseer_recv(virtual_overseer).await,
|
||||
@@ -344,7 +337,6 @@ impl TestState {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn validator_pubkeys(val_ids: &[Sr25519Keyring]) -> Vec<ValidatorId> {
|
||||
val_ids.iter().map(|v| v.public().into()).collect()
|
||||
}
|
||||
@@ -406,9 +398,7 @@ impl Default for TestState {
|
||||
relay_parent_storage_root: Default::default(),
|
||||
};
|
||||
|
||||
let pov = PoV {
|
||||
block_data: BlockData(vec![42; 64]),
|
||||
};
|
||||
let pov = PoV { block_data: BlockData(vec![42; 64]) };
|
||||
|
||||
let available_data = AvailableData {
|
||||
validation_data: persisted_validation_data.clone(),
|
||||
@@ -451,7 +441,8 @@ fn availability_is_recovered_from_chunks_if_no_group_provided() {
|
||||
status: LeafStatus::Fresh,
|
||||
span: Arc::new(jaeger::Span::Disabled),
|
||||
})),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
@@ -462,8 +453,9 @@ fn availability_is_recovered_from_chunks_if_no_group_provided() {
|
||||
test_state.session_index,
|
||||
None,
|
||||
tx,
|
||||
)
|
||||
).await;
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
test_state.test_runtime_api(&mut virtual_overseer).await;
|
||||
|
||||
@@ -472,12 +464,14 @@ fn availability_is_recovered_from_chunks_if_no_group_provided() {
|
||||
test_state.respond_to_available_data_query(&mut virtual_overseer, false).await;
|
||||
test_state.respond_to_query_all_request(&mut virtual_overseer, |_| false).await;
|
||||
|
||||
test_state.test_chunk_requests(
|
||||
candidate_hash,
|
||||
&mut virtual_overseer,
|
||||
test_state.threshold(),
|
||||
|_| Has::Yes,
|
||||
).await;
|
||||
test_state
|
||||
.test_chunk_requests(
|
||||
candidate_hash,
|
||||
&mut virtual_overseer,
|
||||
test_state.threshold(),
|
||||
|_| Has::Yes,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Recovered data should match the original one.
|
||||
assert_eq!(rx.await.unwrap().unwrap(), test_state.available_data);
|
||||
@@ -496,20 +490,23 @@ fn availability_is_recovered_from_chunks_if_no_group_provided() {
|
||||
test_state.session_index,
|
||||
None,
|
||||
tx,
|
||||
)
|
||||
).await;
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
test_state.test_runtime_api(&mut virtual_overseer).await;
|
||||
|
||||
test_state.respond_to_available_data_query(&mut virtual_overseer, false).await;
|
||||
test_state.respond_to_query_all_request(&mut virtual_overseer, |_| false).await;
|
||||
|
||||
test_state.test_chunk_requests(
|
||||
new_candidate.hash(),
|
||||
&mut virtual_overseer,
|
||||
test_state.impossibility_threshold(),
|
||||
|_| Has::No,
|
||||
).await;
|
||||
test_state
|
||||
.test_chunk_requests(
|
||||
new_candidate.hash(),
|
||||
&mut virtual_overseer,
|
||||
test_state.impossibility_threshold(),
|
||||
|_| Has::No,
|
||||
)
|
||||
.await;
|
||||
|
||||
// A request times out with `Unavailable` error.
|
||||
assert_eq!(rx.await.unwrap().unwrap_err(), RecoveryError::Unavailable);
|
||||
@@ -530,7 +527,8 @@ fn availability_is_recovered_from_chunks_even_if_backing_group_supplied_if_chunk
|
||||
status: LeafStatus::Fresh,
|
||||
span: Arc::new(jaeger::Span::Disabled),
|
||||
})),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
@@ -541,8 +539,9 @@ fn availability_is_recovered_from_chunks_even_if_backing_group_supplied_if_chunk
|
||||
test_state.session_index,
|
||||
Some(GroupIndex(0)),
|
||||
tx,
|
||||
)
|
||||
).await;
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
test_state.test_runtime_api(&mut virtual_overseer).await;
|
||||
|
||||
@@ -551,12 +550,14 @@ fn availability_is_recovered_from_chunks_even_if_backing_group_supplied_if_chunk
|
||||
test_state.respond_to_available_data_query(&mut virtual_overseer, false).await;
|
||||
test_state.respond_to_query_all_request(&mut virtual_overseer, |_| false).await;
|
||||
|
||||
test_state.test_chunk_requests(
|
||||
candidate_hash,
|
||||
&mut virtual_overseer,
|
||||
test_state.threshold(),
|
||||
|_| Has::Yes,
|
||||
).await;
|
||||
test_state
|
||||
.test_chunk_requests(
|
||||
candidate_hash,
|
||||
&mut virtual_overseer,
|
||||
test_state.threshold(),
|
||||
|_| Has::Yes,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Recovered data should match the original one.
|
||||
assert_eq!(rx.await.unwrap().unwrap(), test_state.available_data);
|
||||
@@ -575,20 +576,23 @@ fn availability_is_recovered_from_chunks_even_if_backing_group_supplied_if_chunk
|
||||
test_state.session_index,
|
||||
None,
|
||||
tx,
|
||||
)
|
||||
).await;
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
test_state.test_runtime_api(&mut virtual_overseer).await;
|
||||
|
||||
test_state.respond_to_available_data_query(&mut virtual_overseer, false).await;
|
||||
test_state.respond_to_query_all_request(&mut virtual_overseer, |_| false).await;
|
||||
|
||||
test_state.test_chunk_requests(
|
||||
new_candidate.hash(),
|
||||
&mut virtual_overseer,
|
||||
test_state.impossibility_threshold(),
|
||||
|_| Has::No,
|
||||
).await;
|
||||
test_state
|
||||
.test_chunk_requests(
|
||||
new_candidate.hash(),
|
||||
&mut virtual_overseer,
|
||||
test_state.impossibility_threshold(),
|
||||
|_| Has::No,
|
||||
)
|
||||
.await;
|
||||
|
||||
// A request times out with `Unavailable` error.
|
||||
assert_eq!(rx.await.unwrap().unwrap_err(), RecoveryError::Unavailable);
|
||||
@@ -609,7 +613,8 @@ fn bad_merkle_path_leads_to_recovery_error() {
|
||||
status: LeafStatus::Fresh,
|
||||
span: Arc::new(jaeger::Span::Disabled),
|
||||
})),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
@@ -620,8 +625,9 @@ fn bad_merkle_path_leads_to_recovery_error() {
|
||||
test_state.session_index,
|
||||
None,
|
||||
tx,
|
||||
)
|
||||
).await;
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
test_state.test_runtime_api(&mut virtual_overseer).await;
|
||||
|
||||
@@ -637,12 +643,14 @@ fn bad_merkle_path_leads_to_recovery_error() {
|
||||
test_state.respond_to_available_data_query(&mut virtual_overseer, false).await;
|
||||
test_state.respond_to_query_all_request(&mut virtual_overseer, |_| false).await;
|
||||
|
||||
test_state.test_chunk_requests(
|
||||
candidate_hash,
|
||||
&mut virtual_overseer,
|
||||
test_state.impossibility_threshold(),
|
||||
|_| Has::Yes,
|
||||
).await;
|
||||
test_state
|
||||
.test_chunk_requests(
|
||||
candidate_hash,
|
||||
&mut virtual_overseer,
|
||||
test_state.impossibility_threshold(),
|
||||
|_| Has::Yes,
|
||||
)
|
||||
.await;
|
||||
|
||||
// A request times out with `Unavailable` error.
|
||||
assert_eq!(rx.await.unwrap().unwrap_err(), RecoveryError::Unavailable);
|
||||
@@ -663,7 +671,8 @@ fn wrong_chunk_index_leads_to_recovery_error() {
|
||||
status: LeafStatus::Fresh,
|
||||
span: Arc::new(jaeger::Span::Disabled),
|
||||
})),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
@@ -674,8 +683,9 @@ fn wrong_chunk_index_leads_to_recovery_error() {
|
||||
test_state.session_index,
|
||||
None,
|
||||
tx,
|
||||
)
|
||||
).await;
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
test_state.test_runtime_api(&mut virtual_overseer).await;
|
||||
|
||||
@@ -690,12 +700,14 @@ fn wrong_chunk_index_leads_to_recovery_error() {
|
||||
test_state.respond_to_available_data_query(&mut virtual_overseer, false).await;
|
||||
test_state.respond_to_query_all_request(&mut virtual_overseer, |_| false).await;
|
||||
|
||||
test_state.test_chunk_requests(
|
||||
candidate_hash,
|
||||
&mut virtual_overseer,
|
||||
test_state.impossibility_threshold(),
|
||||
|_| Has::No,
|
||||
).await;
|
||||
test_state
|
||||
.test_chunk_requests(
|
||||
candidate_hash,
|
||||
&mut virtual_overseer,
|
||||
test_state.impossibility_threshold(),
|
||||
|_| Has::No,
|
||||
)
|
||||
.await;
|
||||
|
||||
// A request times out with `Unavailable` error as there are no good peers.
|
||||
assert_eq!(rx.await.unwrap().unwrap_err(), RecoveryError::Unavailable);
|
||||
@@ -708,9 +720,7 @@ fn invalid_erasure_coding_leads_to_invalid_error() {
|
||||
let mut test_state = TestState::default();
|
||||
|
||||
test_harness_fast_path(|mut virtual_overseer| async move {
|
||||
let pov = PoV {
|
||||
block_data: BlockData(vec![69; 64]),
|
||||
};
|
||||
let pov = PoV { block_data: BlockData(vec![69; 64]) };
|
||||
|
||||
let (bad_chunks, bad_erasure_root) = derive_erasure_chunks_with_proofs_and_root(
|
||||
test_state.chunks.len(),
|
||||
@@ -734,7 +744,8 @@ fn invalid_erasure_coding_leads_to_invalid_error() {
|
||||
status: LeafStatus::Fresh,
|
||||
span: Arc::new(jaeger::Span::Disabled),
|
||||
})),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
@@ -745,20 +756,23 @@ fn invalid_erasure_coding_leads_to_invalid_error() {
|
||||
test_state.session_index,
|
||||
None,
|
||||
tx,
|
||||
)
|
||||
).await;
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
test_state.test_runtime_api(&mut virtual_overseer).await;
|
||||
|
||||
test_state.respond_to_available_data_query(&mut virtual_overseer, false).await;
|
||||
test_state.respond_to_query_all_request(&mut virtual_overseer, |_| false).await;
|
||||
|
||||
test_state.test_chunk_requests(
|
||||
candidate_hash,
|
||||
&mut virtual_overseer,
|
||||
test_state.threshold(),
|
||||
|_| Has::Yes,
|
||||
).await;
|
||||
test_state
|
||||
.test_chunk_requests(
|
||||
candidate_hash,
|
||||
&mut virtual_overseer,
|
||||
test_state.threshold(),
|
||||
|_| Has::Yes,
|
||||
)
|
||||
.await;
|
||||
|
||||
// f+1 'valid' chunks can't produce correct data.
|
||||
assert_eq!(rx.await.unwrap().unwrap_err(), RecoveryError::Invalid);
|
||||
@@ -779,7 +793,8 @@ fn fast_path_backing_group_recovers() {
|
||||
status: LeafStatus::Fresh,
|
||||
span: Arc::new(jaeger::Span::Disabled),
|
||||
})),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
@@ -790,8 +805,9 @@ fn fast_path_backing_group_recovers() {
|
||||
test_state.session_index,
|
||||
Some(GroupIndex(0)),
|
||||
tx,
|
||||
)
|
||||
).await;
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
test_state.test_runtime_api(&mut virtual_overseer).await;
|
||||
|
||||
@@ -804,11 +820,9 @@ fn fast_path_backing_group_recovers() {
|
||||
|
||||
test_state.respond_to_available_data_query(&mut virtual_overseer, false).await;
|
||||
|
||||
test_state.test_full_data_requests(
|
||||
candidate_hash,
|
||||
&mut virtual_overseer,
|
||||
who_has,
|
||||
).await;
|
||||
test_state
|
||||
.test_full_data_requests(candidate_hash, &mut virtual_overseer, who_has)
|
||||
.await;
|
||||
|
||||
// Recovered data should match the original one.
|
||||
assert_eq!(rx.await.unwrap().unwrap(), test_state.available_data);
|
||||
@@ -829,7 +843,8 @@ fn no_answers_in_fast_path_causes_chunk_requests() {
|
||||
status: LeafStatus::Fresh,
|
||||
span: Arc::new(jaeger::Span::Disabled),
|
||||
})),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
@@ -840,8 +855,9 @@ fn no_answers_in_fast_path_causes_chunk_requests() {
|
||||
test_state.session_index,
|
||||
Some(GroupIndex(0)),
|
||||
tx,
|
||||
)
|
||||
).await;
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
test_state.test_runtime_api(&mut virtual_overseer).await;
|
||||
|
||||
@@ -855,20 +871,20 @@ fn no_answers_in_fast_path_causes_chunk_requests() {
|
||||
|
||||
test_state.respond_to_available_data_query(&mut virtual_overseer, false).await;
|
||||
|
||||
test_state.test_full_data_requests(
|
||||
candidate_hash,
|
||||
&mut virtual_overseer,
|
||||
who_has,
|
||||
).await;
|
||||
test_state
|
||||
.test_full_data_requests(candidate_hash, &mut virtual_overseer, who_has)
|
||||
.await;
|
||||
|
||||
test_state.respond_to_query_all_request(&mut virtual_overseer, |_| false).await;
|
||||
|
||||
test_state.test_chunk_requests(
|
||||
candidate_hash,
|
||||
&mut virtual_overseer,
|
||||
test_state.threshold(),
|
||||
|_| Has::Yes,
|
||||
).await;
|
||||
test_state
|
||||
.test_chunk_requests(
|
||||
candidate_hash,
|
||||
&mut virtual_overseer,
|
||||
test_state.threshold(),
|
||||
|_| Has::Yes,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Recovered data should match the original one.
|
||||
assert_eq!(rx.await.unwrap().unwrap(), test_state.available_data);
|
||||
@@ -889,7 +905,8 @@ fn task_canceled_when_receivers_dropped() {
|
||||
status: LeafStatus::Fresh,
|
||||
span: Arc::new(jaeger::Span::Disabled),
|
||||
})),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
let (tx, _) = oneshot::channel();
|
||||
|
||||
@@ -900,8 +917,9 @@ fn task_canceled_when_receivers_dropped() {
|
||||
test_state.session_index,
|
||||
None,
|
||||
tx,
|
||||
)
|
||||
).await;
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
test_state.test_runtime_api(&mut virtual_overseer).await;
|
||||
|
||||
@@ -929,7 +947,8 @@ fn chunks_retry_until_all_nodes_respond() {
|
||||
status: LeafStatus::Fresh,
|
||||
span: Arc::new(jaeger::Span::Disabled),
|
||||
})),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
@@ -940,8 +959,9 @@ fn chunks_retry_until_all_nodes_respond() {
|
||||
test_state.session_index,
|
||||
Some(GroupIndex(0)),
|
||||
tx,
|
||||
)
|
||||
).await;
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
test_state.test_runtime_api(&mut virtual_overseer).await;
|
||||
|
||||
@@ -950,21 +970,25 @@ fn chunks_retry_until_all_nodes_respond() {
|
||||
test_state.respond_to_available_data_query(&mut virtual_overseer, false).await;
|
||||
test_state.respond_to_query_all_request(&mut virtual_overseer, |_| false).await;
|
||||
|
||||
test_state.test_chunk_requests(
|
||||
candidate_hash,
|
||||
&mut virtual_overseer,
|
||||
test_state.validators.len(),
|
||||
|_| Has::timeout(),
|
||||
).await;
|
||||
test_state
|
||||
.test_chunk_requests(
|
||||
candidate_hash,
|
||||
&mut virtual_overseer,
|
||||
test_state.validators.len(),
|
||||
|_| Has::timeout(),
|
||||
)
|
||||
.await;
|
||||
|
||||
// we get to go another round!
|
||||
|
||||
test_state.test_chunk_requests(
|
||||
candidate_hash,
|
||||
&mut virtual_overseer,
|
||||
test_state.impossibility_threshold(),
|
||||
|_| Has::No,
|
||||
).await;
|
||||
test_state
|
||||
.test_chunk_requests(
|
||||
candidate_hash,
|
||||
&mut virtual_overseer,
|
||||
test_state.impossibility_threshold(),
|
||||
|_| Has::No,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Recovered data should match the original one.
|
||||
assert_eq!(rx.await.unwrap().unwrap_err(), RecoveryError::Unavailable);
|
||||
@@ -985,7 +1009,8 @@ fn returns_early_if_we_have_the_data() {
|
||||
status: LeafStatus::Fresh,
|
||||
span: Arc::new(jaeger::Span::Disabled),
|
||||
})),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
@@ -996,8 +1021,9 @@ fn returns_early_if_we_have_the_data() {
|
||||
test_state.session_index,
|
||||
None,
|
||||
tx,
|
||||
)
|
||||
).await;
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
test_state.test_runtime_api(&mut virtual_overseer).await;
|
||||
test_state.respond_to_available_data_query(&mut virtual_overseer, true).await;
|
||||
@@ -1020,7 +1046,8 @@ fn does_not_query_local_validator() {
|
||||
status: LeafStatus::Fresh,
|
||||
span: Arc::new(jaeger::Span::Disabled),
|
||||
})),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
@@ -1031,8 +1058,9 @@ fn does_not_query_local_validator() {
|
||||
test_state.session_index,
|
||||
None,
|
||||
tx,
|
||||
)
|
||||
).await;
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
test_state.test_runtime_api(&mut virtual_overseer).await;
|
||||
test_state.respond_to_available_data_query(&mut virtual_overseer, false).await;
|
||||
@@ -1040,28 +1068,24 @@ fn does_not_query_local_validator() {
|
||||
|
||||
let candidate_hash = test_state.candidate.hash();
|
||||
|
||||
test_state.test_chunk_requests(
|
||||
candidate_hash,
|
||||
&mut virtual_overseer,
|
||||
test_state.validators.len(),
|
||||
|i| if i == 0 {
|
||||
panic!("requested from local validator")
|
||||
} else {
|
||||
Has::timeout()
|
||||
},
|
||||
).await;
|
||||
test_state
|
||||
.test_chunk_requests(
|
||||
candidate_hash,
|
||||
&mut virtual_overseer,
|
||||
test_state.validators.len(),
|
||||
|i| if i == 0 { panic!("requested from local validator") } else { Has::timeout() },
|
||||
)
|
||||
.await;
|
||||
|
||||
// second round, make sure it uses the local chunk.
|
||||
test_state.test_chunk_requests(
|
||||
candidate_hash,
|
||||
&mut virtual_overseer,
|
||||
test_state.threshold() - 1,
|
||||
|i| if i == 0 {
|
||||
panic!("requested from local validator")
|
||||
} else {
|
||||
Has::Yes
|
||||
},
|
||||
).await;
|
||||
test_state
|
||||
.test_chunk_requests(
|
||||
candidate_hash,
|
||||
&mut virtual_overseer,
|
||||
test_state.threshold() - 1,
|
||||
|i| if i == 0 { panic!("requested from local validator") } else { Has::Yes },
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(rx.await.unwrap().unwrap(), test_state.available_data);
|
||||
virtual_overseer
|
||||
|
||||
@@ -24,19 +24,19 @@
|
||||
|
||||
use futures::{channel::oneshot, FutureExt};
|
||||
|
||||
use polkadot_subsystem::messages::*;
|
||||
use polkadot_subsystem::{
|
||||
PerLeafSpan, ActiveLeavesUpdate, FromOverseer, OverseerSignal, SpawnedSubsystem,
|
||||
SubsystemContext, SubsystemResult, SubsystemError,
|
||||
jaeger,
|
||||
overseer,
|
||||
use polkadot_node_network_protocol::{
|
||||
v1 as protocol_v1, OurView, PeerId, UnifiedReputationChange as Rep, View,
|
||||
};
|
||||
use polkadot_node_subsystem_util::{
|
||||
self as util,
|
||||
metrics::{self, prometheus},
|
||||
self as util, MIN_GOSSIP_PEERS,
|
||||
MIN_GOSSIP_PEERS,
|
||||
};
|
||||
use polkadot_primitives::v1::{Hash, SignedAvailabilityBitfield, SigningContext, ValidatorId};
|
||||
use polkadot_node_network_protocol::{v1 as protocol_v1, PeerId, View, UnifiedReputationChange as Rep, OurView};
|
||||
use polkadot_subsystem::{
|
||||
jaeger, messages::*, overseer, ActiveLeavesUpdate, FromOverseer, OverseerSignal, PerLeafSpan,
|
||||
SpawnedSubsystem, SubsystemContext, SubsystemError, SubsystemResult,
|
||||
};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -46,8 +46,10 @@ const COST_SIGNATURE_INVALID: Rep = Rep::CostMajor("Bitfield signature invalid")
|
||||
const COST_VALIDATOR_INDEX_INVALID: Rep = Rep::CostMajor("Bitfield validator index invalid");
|
||||
const COST_MISSING_PEER_SESSION_KEY: Rep = Rep::CostMinor("Missing peer session key");
|
||||
const COST_NOT_IN_VIEW: Rep = Rep::CostMinor("Not interested in that parent hash");
|
||||
const COST_PEER_DUPLICATE_MESSAGE: Rep = Rep::CostMinorRepeated("Peer sent the same message multiple times");
|
||||
const BENEFIT_VALID_MESSAGE_FIRST: Rep = Rep::BenefitMinorFirst("Valid message with new information");
|
||||
const COST_PEER_DUPLICATE_MESSAGE: Rep =
|
||||
Rep::CostMinorRepeated("Peer sent the same message multiple times");
|
||||
const BENEFIT_VALID_MESSAGE_FIRST: Rep =
|
||||
Rep::BenefitMinorFirst("Valid message with new information");
|
||||
const BENEFIT_VALID_MESSAGE: Rep = Rep::BenefitMinor("Valid message");
|
||||
|
||||
/// Checked signed availability bitfield that is distributed
|
||||
@@ -62,14 +64,10 @@ struct BitfieldGossipMessage {
|
||||
|
||||
impl BitfieldGossipMessage {
|
||||
fn into_validation_protocol(self) -> protocol_v1::ValidationProtocol {
|
||||
protocol_v1::ValidationProtocol::BitfieldDistribution(
|
||||
self.into_network_message()
|
||||
)
|
||||
protocol_v1::ValidationProtocol::BitfieldDistribution(self.into_network_message())
|
||||
}
|
||||
|
||||
fn into_network_message(self)
|
||||
-> protocol_v1::BitfieldDistributionMessage
|
||||
{
|
||||
fn into_network_message(self) -> protocol_v1::BitfieldDistributionMessage {
|
||||
protocol_v1::BitfieldDistributionMessage::Bitfield(
|
||||
self.relay_parent,
|
||||
self.signed_availability.into(),
|
||||
@@ -124,7 +122,11 @@ struct PerRelayParentData {
|
||||
|
||||
impl PerRelayParentData {
|
||||
/// Create a new instance.
|
||||
fn new(signing_context: SigningContext, validator_set: Vec<ValidatorId>, span: PerLeafSpan) -> Self {
|
||||
fn new(
|
||||
signing_context: SigningContext,
|
||||
validator_set: Vec<ValidatorId>,
|
||||
span: PerLeafSpan,
|
||||
) -> Self {
|
||||
Self {
|
||||
signing_context,
|
||||
validator_set,
|
||||
@@ -141,8 +143,14 @@ impl PerRelayParentData {
|
||||
peer: &PeerId,
|
||||
validator: &ValidatorId,
|
||||
) -> bool {
|
||||
self.message_sent_to_peer.get(peer).map(|v| !v.contains(validator)).unwrap_or(true)
|
||||
&& self.message_received_from_peer.get(peer).map(|v| !v.contains(validator)).unwrap_or(true)
|
||||
self.message_sent_to_peer
|
||||
.get(peer)
|
||||
.map(|v| !v.contains(validator))
|
||||
.unwrap_or(true) &&
|
||||
self.message_received_from_peer
|
||||
.get(peer)
|
||||
.map(|v| !v.contains(validator))
|
||||
.unwrap_or(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,34 +180,34 @@ impl BitfieldDistribution {
|
||||
Ok(message) => message,
|
||||
Err(e) => {
|
||||
tracing::debug!(target: LOG_TARGET, err = ?e, "Failed to receive a message from Overseer, exiting");
|
||||
return;
|
||||
return
|
||||
},
|
||||
};
|
||||
match message {
|
||||
FromOverseer::Communication {
|
||||
msg: BitfieldDistributionMessage::DistributeBitfield(hash, signed_availability),
|
||||
} => {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?hash,
|
||||
"Processing DistributeBitfield"
|
||||
);
|
||||
tracing::trace!(target: LOG_TARGET, ?hash, "Processing DistributeBitfield");
|
||||
handle_bitfield_distribution(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&self.metrics,
|
||||
hash,
|
||||
signed_availability,
|
||||
).await;
|
||||
}
|
||||
)
|
||||
.await;
|
||||
},
|
||||
FromOverseer::Communication {
|
||||
msg: BitfieldDistributionMessage::NetworkBridgeUpdateV1(event),
|
||||
} => {
|
||||
tracing::trace!(target: LOG_TARGET, "Processing NetworkMessage");
|
||||
// a network message was received
|
||||
handle_network_msg(&mut ctx, &mut state, &self.metrics, event).await;
|
||||
}
|
||||
FromOverseer::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { activated, .. })) => {
|
||||
},
|
||||
FromOverseer::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate {
|
||||
activated,
|
||||
..
|
||||
})) => {
|
||||
let _timer = self.metrics.time_active_leaves_update();
|
||||
|
||||
for activated in activated {
|
||||
@@ -221,41 +229,34 @@ impl BitfieldDistribution {
|
||||
relay_parent,
|
||||
PerRelayParentData::new(signing_context, validator_set, span),
|
||||
);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::warn!(target: LOG_TARGET, err = ?e, "query_basics has failed");
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
FromOverseer::Signal(OverseerSignal::BlockFinalized(hash, number)) => {
|
||||
tracing::trace!(target: LOG_TARGET, hash = %hash, number = %number, "block finalized");
|
||||
}
|
||||
},
|
||||
FromOverseer::Signal(OverseerSignal::Conclude) => {
|
||||
tracing::trace!(target: LOG_TARGET, "Conclude");
|
||||
return;
|
||||
}
|
||||
return
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Modify the reputation of a peer based on its behavior.
|
||||
async fn modify_reputation<Context>(
|
||||
ctx: &mut Context,
|
||||
peer: PeerId,
|
||||
rep: Rep,
|
||||
)
|
||||
async fn modify_reputation<Context>(ctx: &mut Context, peer: PeerId, rep: Rep)
|
||||
where
|
||||
Context: SubsystemContext<Message = BitfieldDistributionMessage>,
|
||||
{
|
||||
tracing::trace!(target: LOG_TARGET, ?rep, peer_id = %peer, "reputation change");
|
||||
|
||||
ctx.send_message(
|
||||
NetworkBridgeMessage::ReportPeer(peer, rep),
|
||||
)
|
||||
.await
|
||||
ctx.send_message(NetworkBridgeMessage::ReportPeer(peer, rep)).await
|
||||
}
|
||||
|
||||
/// Distribute a given valid and signature checked bitfield message.
|
||||
@@ -267,8 +268,7 @@ async fn handle_bitfield_distribution<Context>(
|
||||
metrics: &Metrics,
|
||||
relay_parent: Hash,
|
||||
signed_availability: SignedAvailabilityBitfield,
|
||||
)
|
||||
where
|
||||
) where
|
||||
Context: SubsystemContext<Message = BitfieldDistributionMessage>,
|
||||
{
|
||||
let _timer = metrics.time_handle_bitfield_distribution();
|
||||
@@ -284,26 +284,27 @@ where
|
||||
"Not supposed to work on relay parent related data",
|
||||
);
|
||||
|
||||
return;
|
||||
return
|
||||
};
|
||||
let validator_set = &job_data.validator_set;
|
||||
if validator_set.is_empty() {
|
||||
tracing::trace!(target: LOG_TARGET, relay_parent = %relay_parent, "validator set is empty");
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
let validator_index = signed_availability.validator_index().0 as usize;
|
||||
let validator = if let Some(validator) = validator_set.get(validator_index) {
|
||||
validator.clone()
|
||||
} else {
|
||||
tracing::trace!(target: LOG_TARGET, "Could not find a validator for index {}", validator_index);
|
||||
return;
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
"Could not find a validator for index {}",
|
||||
validator_index
|
||||
);
|
||||
return
|
||||
};
|
||||
|
||||
let msg = BitfieldGossipMessage {
|
||||
relay_parent,
|
||||
signed_availability,
|
||||
};
|
||||
let msg = BitfieldGossipMessage { relay_parent, signed_availability };
|
||||
|
||||
let gossip_peers = &state.gossip_peers;
|
||||
let peer_views = &mut state.peer_views;
|
||||
@@ -322,23 +323,17 @@ async fn relay_message<Context>(
|
||||
peer_views: &mut HashMap<PeerId, View>,
|
||||
validator: ValidatorId,
|
||||
message: BitfieldGossipMessage,
|
||||
)
|
||||
where
|
||||
) where
|
||||
Context: SubsystemContext<Message = BitfieldDistributionMessage>,
|
||||
{
|
||||
let span = job_data.span.child("relay-msg");
|
||||
|
||||
let _span = span.child("provisionable");
|
||||
// notify the overseer about a new and valid signed bitfield
|
||||
ctx.send_message(
|
||||
ProvisionerMessage::ProvisionableData(
|
||||
message.relay_parent,
|
||||
ProvisionableData::Bitfield(
|
||||
message.relay_parent,
|
||||
message.signed_availability.clone(),
|
||||
),
|
||||
),
|
||||
)
|
||||
ctx.send_message(ProvisionerMessage::ProvisionableData(
|
||||
message.relay_parent,
|
||||
ProvisionableData::Bitfield(message.relay_parent, message.signed_availability.clone()),
|
||||
))
|
||||
.await;
|
||||
|
||||
drop(_span);
|
||||
@@ -350,7 +345,8 @@ where
|
||||
.filter_map(|(peer, view)| {
|
||||
// check interest in the peer in this message's relay parent
|
||||
if view.contains(&message.relay_parent) {
|
||||
let message_needed = job_data.message_from_validator_needed_by_peer(&peer, &validator);
|
||||
let message_needed =
|
||||
job_data.message_from_validator_needed_by_peer(&peer, &validator);
|
||||
if message_needed {
|
||||
Some(peer.clone())
|
||||
} else {
|
||||
@@ -366,14 +362,14 @@ where
|
||||
interested_peers,
|
||||
MIN_GOSSIP_PEERS,
|
||||
);
|
||||
interested_peers.iter()
|
||||
.for_each(|peer|{
|
||||
// track the message as sent for this peer
|
||||
job_data.message_sent_to_peer
|
||||
.entry(peer.clone())
|
||||
.or_default()
|
||||
.insert(validator.clone());
|
||||
});
|
||||
interested_peers.iter().for_each(|peer| {
|
||||
// track the message as sent for this peer
|
||||
job_data
|
||||
.message_sent_to_peer
|
||||
.entry(peer.clone())
|
||||
.or_default()
|
||||
.insert(validator.clone());
|
||||
});
|
||||
|
||||
drop(_span);
|
||||
|
||||
@@ -385,12 +381,10 @@ where
|
||||
);
|
||||
} else {
|
||||
let _span = span.child("gossip");
|
||||
ctx.send_message(
|
||||
NetworkBridgeMessage::SendValidationMessage(
|
||||
interested_peers,
|
||||
message.into_validation_protocol(),
|
||||
),
|
||||
)
|
||||
ctx.send_message(NetworkBridgeMessage::SendValidationMessage(
|
||||
interested_peers,
|
||||
message.into_validation_protocol(),
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
@@ -402,8 +396,7 @@ async fn process_incoming_peer_message<Context>(
|
||||
metrics: &Metrics,
|
||||
origin: PeerId,
|
||||
message: protocol_v1::BitfieldDistributionMessage,
|
||||
)
|
||||
where
|
||||
) where
|
||||
Context: SubsystemContext<Message = BitfieldDistributionMessage>,
|
||||
{
|
||||
let protocol_v1::BitfieldDistributionMessage::Bitfield(relay_parent, bitfield) = message;
|
||||
@@ -416,7 +409,7 @@ where
|
||||
// we don't care about this, not part of our view.
|
||||
if !state.view.contains(&relay_parent) {
|
||||
modify_reputation(ctx, origin, COST_NOT_IN_VIEW).await;
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
// Ignore anything the overseer did not tell this subsystem to work on.
|
||||
@@ -425,16 +418,17 @@ where
|
||||
job_data
|
||||
} else {
|
||||
modify_reputation(ctx, origin, COST_NOT_IN_VIEW).await;
|
||||
return;
|
||||
return
|
||||
};
|
||||
|
||||
let validator_index = bitfield.unchecked_validator_index();
|
||||
|
||||
let mut _span = job_data.span
|
||||
.child("msg-received")
|
||||
.with_peer_id(&origin)
|
||||
.with_claimed_validator_index(validator_index)
|
||||
.with_stage(jaeger::Stage::BitfieldDistribution);
|
||||
let mut _span = job_data
|
||||
.span
|
||||
.child("msg-received")
|
||||
.with_peer_id(&origin)
|
||||
.with_claimed_validator_index(validator_index)
|
||||
.with_stage(jaeger::Stage::BitfieldDistribution);
|
||||
|
||||
let validator_set = &job_data.validator_set;
|
||||
if validator_set.is_empty() {
|
||||
@@ -445,7 +439,7 @@ where
|
||||
"Validator set is empty",
|
||||
);
|
||||
modify_reputation(ctx, origin, COST_MISSING_PEER_SESSION_KEY).await;
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
// Use the (untrusted) validator index provided by the signed payload
|
||||
@@ -455,28 +449,20 @@ where
|
||||
validator.clone()
|
||||
} else {
|
||||
modify_reputation(ctx, origin, COST_VALIDATOR_INDEX_INVALID).await;
|
||||
return;
|
||||
return
|
||||
};
|
||||
|
||||
// Check if the peer already sent us a message for the validator denoted in the message earlier.
|
||||
// Must be done after validator index verification, in order to avoid storing an unbounded
|
||||
// number of set entries.
|
||||
let received_set = job_data
|
||||
.message_received_from_peer
|
||||
.entry(origin.clone())
|
||||
.or_default();
|
||||
let received_set = job_data.message_received_from_peer.entry(origin.clone()).or_default();
|
||||
|
||||
if !received_set.contains(&validator) {
|
||||
received_set.insert(validator.clone());
|
||||
} else {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?validator_index,
|
||||
?origin,
|
||||
"Duplicate message",
|
||||
);
|
||||
tracing::trace!(target: LOG_TARGET, ?validator_index, ?origin, "Duplicate message",);
|
||||
modify_reputation(ctx, origin, COST_PEER_DUPLICATE_MESSAGE).await;
|
||||
return;
|
||||
return
|
||||
};
|
||||
|
||||
let one_per_validator = &mut (job_data.one_per_validator);
|
||||
@@ -491,25 +477,23 @@ where
|
||||
if old_message.signed_availability.as_unchecked() == &bitfield {
|
||||
modify_reputation(ctx, origin, BENEFIT_VALID_MESSAGE).await;
|
||||
}
|
||||
return;
|
||||
return
|
||||
}
|
||||
let signed_availability = match bitfield.try_into_checked(&signing_context, &validator) {
|
||||
Err(_) => {
|
||||
modify_reputation(ctx, origin, COST_SIGNATURE_INVALID).await;
|
||||
return;
|
||||
return
|
||||
},
|
||||
Ok(bitfield) => bitfield,
|
||||
};
|
||||
|
||||
let message = BitfieldGossipMessage {
|
||||
relay_parent,
|
||||
signed_availability,
|
||||
};
|
||||
let message = BitfieldGossipMessage { relay_parent, signed_availability };
|
||||
|
||||
metrics.on_bitfield_received();
|
||||
one_per_validator.insert(validator.clone(), message.clone());
|
||||
|
||||
relay_message(ctx, job_data, &state.gossip_peers, &mut state.peer_views, validator, message).await;
|
||||
relay_message(ctx, job_data, &state.gossip_peers, &mut state.peer_views, validator, message)
|
||||
.await;
|
||||
|
||||
modify_reputation(ctx, origin, BENEFIT_VALID_MESSAGE_FIRST).await
|
||||
}
|
||||
@@ -521,32 +505,22 @@ async fn handle_network_msg<Context>(
|
||||
state: &mut ProtocolState,
|
||||
metrics: &Metrics,
|
||||
bridge_message: NetworkBridgeEvent<protocol_v1::BitfieldDistributionMessage>,
|
||||
)
|
||||
where
|
||||
) where
|
||||
Context: SubsystemContext<Message = BitfieldDistributionMessage>,
|
||||
{
|
||||
let _timer = metrics.time_handle_network_msg();
|
||||
|
||||
match bridge_message {
|
||||
NetworkBridgeEvent::PeerConnected(peerid, role, _) => {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?peerid,
|
||||
?role,
|
||||
"Peer connected",
|
||||
);
|
||||
tracing::trace!(target: LOG_TARGET, ?peerid, ?role, "Peer connected",);
|
||||
// insert if none already present
|
||||
state.peer_views.entry(peerid).or_default();
|
||||
}
|
||||
},
|
||||
NetworkBridgeEvent::PeerDisconnected(peerid) => {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?peerid,
|
||||
"Peer disconnected",
|
||||
);
|
||||
tracing::trace!(target: LOG_TARGET, ?peerid, "Peer disconnected",);
|
||||
// get rid of superfluous data
|
||||
state.peer_views.remove(&peerid);
|
||||
}
|
||||
},
|
||||
NetworkBridgeEvent::NewGossipTopology(peers) => {
|
||||
let newly_added: Vec<PeerId> = peers.difference(&state.gossip_peers).cloned().collect();
|
||||
state.gossip_peers = peers;
|
||||
@@ -555,24 +529,15 @@ where
|
||||
handle_peer_view_change(ctx, state, peer, view).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
NetworkBridgeEvent::PeerViewChange(peerid, view) => {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?peerid,
|
||||
?view,
|
||||
"Peer view change",
|
||||
);
|
||||
tracing::trace!(target: LOG_TARGET, ?peerid, ?view, "Peer view change",);
|
||||
handle_peer_view_change(ctx, state, peerid, view).await;
|
||||
}
|
||||
},
|
||||
NetworkBridgeEvent::OurViewChange(view) => {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?view,
|
||||
"Our view change",
|
||||
);
|
||||
tracing::trace!(target: LOG_TARGET, ?view, "Our view change",);
|
||||
handle_our_view_change(state, view);
|
||||
}
|
||||
},
|
||||
NetworkBridgeEvent::PeerMessage(remote, message) =>
|
||||
process_incoming_peer_message(ctx, state, metrics, remote, message).await,
|
||||
}
|
||||
@@ -598,7 +563,6 @@ fn handle_our_view_change(state: &mut ProtocolState, view: OurView) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Send the difference between two views which were not sent
|
||||
// to that particular peer.
|
||||
async fn handle_peer_view_change<Context>(
|
||||
@@ -606,25 +570,27 @@ async fn handle_peer_view_change<Context>(
|
||||
state: &mut ProtocolState,
|
||||
origin: PeerId,
|
||||
view: View,
|
||||
)
|
||||
where
|
||||
) where
|
||||
Context: SubsystemContext<Message = BitfieldDistributionMessage>,
|
||||
{
|
||||
let added = state.peer_views.entry(origin.clone()).or_default().replace_difference(view).cloned().collect::<Vec<_>>();
|
||||
let added = state
|
||||
.peer_views
|
||||
.entry(origin.clone())
|
||||
.or_default()
|
||||
.replace_difference(view)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let is_gossip_peer = state.gossip_peers.contains(&origin);
|
||||
let lucky = is_gossip_peer || util::gen_ratio(
|
||||
util::MIN_GOSSIP_PEERS.saturating_sub(state.gossip_peers.len()),
|
||||
util::MIN_GOSSIP_PEERS,
|
||||
);
|
||||
let lucky = is_gossip_peer ||
|
||||
util::gen_ratio(
|
||||
util::MIN_GOSSIP_PEERS.saturating_sub(state.gossip_peers.len()),
|
||||
util::MIN_GOSSIP_PEERS,
|
||||
);
|
||||
|
||||
if !lucky {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?origin,
|
||||
"Peer view change is ignored",
|
||||
);
|
||||
return;
|
||||
tracing::trace!(target: LOG_TARGET, ?origin, "Peer view change is ignored",);
|
||||
return
|
||||
}
|
||||
|
||||
// Send all messages we've seen before and the peer is now interested
|
||||
@@ -637,14 +603,10 @@ where
|
||||
// to the peer `origin`...
|
||||
let one_per_validator = job_data.one_per_validator.clone();
|
||||
let origin = origin.clone();
|
||||
Some(
|
||||
one_per_validator
|
||||
.into_iter()
|
||||
.filter(move |(validator, _message)| {
|
||||
// ..except for the ones the peer already has.
|
||||
job_data.message_from_validator_needed_by_peer(&origin, validator)
|
||||
}),
|
||||
)
|
||||
Some(one_per_validator.into_iter().filter(move |(validator, _message)| {
|
||||
// ..except for the ones the peer already has.
|
||||
job_data.message_from_validator_needed_by_peer(&origin, validator)
|
||||
}))
|
||||
} else {
|
||||
// A relay parent is in the peers view, which is not in ours, ignore those.
|
||||
None
|
||||
@@ -665,14 +627,13 @@ async fn send_tracked_gossip_message<Context>(
|
||||
dest: PeerId,
|
||||
validator: ValidatorId,
|
||||
message: BitfieldGossipMessage,
|
||||
)
|
||||
where
|
||||
) where
|
||||
Context: SubsystemContext<Message = BitfieldDistributionMessage>,
|
||||
{
|
||||
let job_data = if let Some(job_data) = state.per_relay_parent.get_mut(&message.relay_parent) {
|
||||
job_data
|
||||
} else {
|
||||
return;
|
||||
return
|
||||
};
|
||||
|
||||
let _span = job_data.span.child("gossip");
|
||||
@@ -684,17 +645,17 @@ where
|
||||
"Sending gossip message"
|
||||
);
|
||||
|
||||
job_data.message_sent_to_peer
|
||||
job_data
|
||||
.message_sent_to_peer
|
||||
.entry(dest.clone())
|
||||
.or_default()
|
||||
.insert(validator.clone());
|
||||
|
||||
ctx.send_message(
|
||||
NetworkBridgeMessage::SendValidationMessage(
|
||||
vec![dest],
|
||||
message.into_validation_protocol(),
|
||||
),
|
||||
).await;
|
||||
ctx.send_message(NetworkBridgeMessage::SendValidationMessage(
|
||||
vec![dest],
|
||||
message.into_validation_protocol(),
|
||||
))
|
||||
.await;
|
||||
}
|
||||
|
||||
impl<Context> overseer::Subsystem<Context, SubsystemError> for BitfieldDistribution
|
||||
@@ -703,14 +664,9 @@ where
|
||||
Context: overseer::SubsystemContext<Message = BitfieldDistributionMessage>,
|
||||
{
|
||||
fn start(self, ctx: Context) -> SpawnedSubsystem {
|
||||
let future = self.run(ctx)
|
||||
.map(|_| Ok(()))
|
||||
.boxed();
|
||||
let future = self.run(ctx).map(|_| Ok(())).boxed();
|
||||
|
||||
SpawnedSubsystem {
|
||||
name: "bitfield-distribution-subsystem",
|
||||
future,
|
||||
}
|
||||
SpawnedSubsystem { name: "bitfield-distribution-subsystem", future }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -729,23 +685,23 @@ where
|
||||
ctx.send_message(RuntimeApiMessage::Request(
|
||||
relay_parent.clone(),
|
||||
RuntimeApiRequest::Validators(validators_tx),
|
||||
)).await;
|
||||
))
|
||||
.await;
|
||||
|
||||
// query signing context
|
||||
ctx.send_message(RuntimeApiMessage::Request(
|
||||
relay_parent.clone(),
|
||||
RuntimeApiRequest::SessionIndexForChild(session_tx),
|
||||
)).await;
|
||||
))
|
||||
.await;
|
||||
|
||||
match (validators_rx.await?, session_rx.await?) {
|
||||
(Ok(v), Ok(s)) => Ok(Some((
|
||||
v,
|
||||
SigningContext { parent_hash: relay_parent, session_index: s },
|
||||
))),
|
||||
(Ok(v), Ok(s)) =>
|
||||
Ok(Some((v, SigningContext { parent_hash: relay_parent, session_index: s }))),
|
||||
(Err(e), _) | (_, Err(e)) => {
|
||||
tracing::warn!(target: LOG_TARGET, err = ?e, "Failed to fetch basics from runtime API");
|
||||
Ok(None)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -781,8 +737,12 @@ impl Metrics {
|
||||
}
|
||||
|
||||
/// Provide a timer for `handle_bitfield_distribution` which observes on drop.
|
||||
fn time_handle_bitfield_distribution(&self) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
|
||||
self.0.as_ref().map(|metrics| metrics.handle_bitfield_distribution.start_timer())
|
||||
fn time_handle_bitfield_distribution(
|
||||
&self,
|
||||
) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
|
||||
self.0
|
||||
.as_ref()
|
||||
.map(|metrics| metrics.handle_bitfield_distribution.start_timer())
|
||||
}
|
||||
|
||||
/// Provide a timer for `handle_network_msg` which observes on drop.
|
||||
@@ -797,42 +757,36 @@ impl metrics::Metrics for Metrics {
|
||||
gossipped_own_availability_bitfields: prometheus::register(
|
||||
prometheus::Counter::new(
|
||||
"parachain_gossipped_own_availabilty_bitfields_total",
|
||||
"Number of own availability bitfields sent to other peers."
|
||||
"Number of own availability bitfields sent to other peers.",
|
||||
)?,
|
||||
registry,
|
||||
)?,
|
||||
received_availability_bitfields: prometheus::register(
|
||||
prometheus::Counter::new(
|
||||
"parachain_received_availabilty_bitfields_total",
|
||||
"Number of valid availability bitfields received from other peers."
|
||||
"Number of valid availability bitfields received from other peers.",
|
||||
)?,
|
||||
registry,
|
||||
)?,
|
||||
active_leaves_update: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_bitfield_distribution_active_leaves_update",
|
||||
"Time spent within `bitfield_distribution::active_leaves_update`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_bitfield_distribution_active_leaves_update",
|
||||
"Time spent within `bitfield_distribution::active_leaves_update`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
handle_bitfield_distribution: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_bitfield_distribution_handle_bitfield_distribution",
|
||||
"Time spent within `bitfield_distribution::handle_bitfield_distribution`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_bitfield_distribution_handle_bitfield_distribution",
|
||||
"Time spent within `bitfield_distribution::handle_bitfield_distribution`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
handle_network_msg: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_bitfield_distribution_handle_network_msg",
|
||||
"Time spent within `bitfield_distribution::handle_network_msg`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_bitfield_distribution_handle_network_msg",
|
||||
"Time spent within `bitfield_distribution::handle_network_msg`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
};
|
||||
|
||||
@@ -15,28 +15,24 @@
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
use assert_matches::assert_matches;
|
||||
use bitvec::bitvec;
|
||||
use futures::executor;
|
||||
use maplit::hashmap;
|
||||
use polkadot_primitives::v1::{Signed, AvailabilityBitfield, ValidatorIndex};
|
||||
use polkadot_node_network_protocol::{our_view, view, ObservedRole};
|
||||
use polkadot_node_subsystem_test_helpers::make_subsystem_context;
|
||||
use polkadot_node_subsystem_util::TimeoutExt;
|
||||
use sp_keystore::{SyncCryptoStorePtr, SyncCryptoStore};
|
||||
use sp_application_crypto::AppKey;
|
||||
use sp_keystore::testing::KeyStore;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::iter::FromIterator as _;
|
||||
use assert_matches::assert_matches;
|
||||
use polkadot_node_network_protocol::{view, ObservedRole, our_view};
|
||||
use polkadot_primitives::v1::{AvailabilityBitfield, Signed, ValidatorIndex};
|
||||
use polkadot_subsystem::jaeger;
|
||||
use sp_application_crypto::AppKey;
|
||||
use sp_keystore::{testing::KeyStore, SyncCryptoStore, SyncCryptoStorePtr};
|
||||
use std::{iter::FromIterator as _, sync::Arc, time::Duration};
|
||||
|
||||
macro_rules! launch {
|
||||
($fut:expr) => {
|
||||
$fut
|
||||
.timeout(Duration::from_millis(10))
|
||||
.await
|
||||
.expect("10ms is more than enough for sending messages.")
|
||||
$fut.timeout(Duration::from_millis(10))
|
||||
.await
|
||||
.expect("10ms is more than enough for sending messages.")
|
||||
};
|
||||
}
|
||||
|
||||
@@ -64,11 +60,7 @@ fn prewarmed_state(
|
||||
span: PerLeafSpan::new(Arc::new(jaeger::Span::Disabled), "test"),
|
||||
},
|
||||
},
|
||||
peer_views: peers
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|peer| (peer, view!(relay_parent)))
|
||||
.collect(),
|
||||
peer_views: peers.iter().cloned().map(|peer| (peer, view!(relay_parent))).collect(),
|
||||
gossip_peers: peers.into_iter().collect(),
|
||||
view: our_view!(relay_parent),
|
||||
}
|
||||
@@ -80,26 +72,28 @@ fn state_with_view(
|
||||
) -> (ProtocolState, SigningContext, SyncCryptoStorePtr, ValidatorId) {
|
||||
let mut state = ProtocolState::default();
|
||||
|
||||
let signing_context = SigningContext {
|
||||
session_index: 1,
|
||||
parent_hash: relay_parent.clone(),
|
||||
};
|
||||
let signing_context = SigningContext { session_index: 1, parent_hash: relay_parent.clone() };
|
||||
|
||||
let keystore : SyncCryptoStorePtr = Arc::new(KeyStore::new());
|
||||
let keystore: SyncCryptoStorePtr = Arc::new(KeyStore::new());
|
||||
let validator = SyncCryptoStore::sr25519_generate_new(&*keystore, ValidatorId::ID, None)
|
||||
.expect("generating sr25519 key not to fail");
|
||||
|
||||
state.per_relay_parent = view.iter().map(|relay_parent| {(
|
||||
relay_parent.clone(),
|
||||
PerRelayParentData {
|
||||
signing_context: signing_context.clone(),
|
||||
validator_set: vec![validator.clone().into()],
|
||||
one_per_validator: hashmap!{},
|
||||
message_received_from_peer: hashmap!{},
|
||||
message_sent_to_peer: hashmap!{},
|
||||
span: PerLeafSpan::new(Arc::new(jaeger::Span::Disabled), "test"),
|
||||
})
|
||||
}).collect();
|
||||
state.per_relay_parent = view
|
||||
.iter()
|
||||
.map(|relay_parent| {
|
||||
(
|
||||
relay_parent.clone(),
|
||||
PerRelayParentData {
|
||||
signing_context: signing_context.clone(),
|
||||
validator_set: vec![validator.clone().into()],
|
||||
one_per_validator: hashmap! {},
|
||||
message_received_from_peer: hashmap! {},
|
||||
message_sent_to_peer: hashmap! {},
|
||||
span: PerLeafSpan::new(Arc::new(jaeger::Span::Disabled), "test"),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
state.view = view;
|
||||
|
||||
@@ -119,13 +113,10 @@ fn receive_invalid_signature() {
|
||||
let peer_b = PeerId::random();
|
||||
assert_ne!(peer_a, peer_b);
|
||||
|
||||
let signing_context = SigningContext {
|
||||
session_index: 1,
|
||||
parent_hash: hash_a.clone(),
|
||||
};
|
||||
let signing_context = SigningContext { session_index: 1, parent_hash: hash_a.clone() };
|
||||
|
||||
// another validator not part of the validatorset
|
||||
let keystore : SyncCryptoStorePtr = Arc::new(KeyStore::new());
|
||||
let keystore: SyncCryptoStorePtr = Arc::new(KeyStore::new());
|
||||
let malicious = SyncCryptoStore::sr25519_generate_new(&*keystore, ValidatorId::ID, None)
|
||||
.expect("Malicious key created");
|
||||
let validator_0 = SyncCryptoStore::sr25519_generate_new(&*keystore, ValidatorId::ID, None)
|
||||
@@ -140,14 +131,20 @@ fn receive_invalid_signature() {
|
||||
&signing_context,
|
||||
ValidatorIndex(0),
|
||||
&malicious.into(),
|
||||
)).ok().flatten().expect("should be signed");
|
||||
))
|
||||
.ok()
|
||||
.flatten()
|
||||
.expect("should be signed");
|
||||
let invalid_signed_2 = executor::block_on(Signed::<AvailabilityBitfield>::sign(
|
||||
&keystore,
|
||||
payload.clone(),
|
||||
&signing_context,
|
||||
ValidatorIndex(1),
|
||||
&malicious.into(),
|
||||
)).ok().flatten().expect("should be signed");
|
||||
))
|
||||
.ok()
|
||||
.flatten()
|
||||
.expect("should be signed");
|
||||
|
||||
let valid_signed = executor::block_on(Signed::<AvailabilityBitfield>::sign(
|
||||
&keystore,
|
||||
@@ -155,7 +152,10 @@ fn receive_invalid_signature() {
|
||||
&signing_context,
|
||||
ValidatorIndex(0),
|
||||
&validator_0.into(),
|
||||
)).ok().flatten().expect("should be signed");
|
||||
))
|
||||
.ok()
|
||||
.flatten()
|
||||
.expect("should be signed");
|
||||
|
||||
let invalid_msg = BitfieldGossipMessage {
|
||||
relay_parent: hash_a.clone(),
|
||||
@@ -171,8 +171,7 @@ fn receive_invalid_signature() {
|
||||
};
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (mut ctx, mut handle) =
|
||||
make_subsystem_context::<BitfieldDistributionMessage, _>(pool);
|
||||
let (mut ctx, mut handle) = make_subsystem_context::<BitfieldDistributionMessage, _>(pool);
|
||||
|
||||
let mut state = prewarmed_state(
|
||||
validator_0.into(),
|
||||
@@ -180,7 +179,9 @@ fn receive_invalid_signature() {
|
||||
valid_msg,
|
||||
vec![peer_b.clone()],
|
||||
);
|
||||
state.per_relay_parent.get_mut(&hash_a)
|
||||
state
|
||||
.per_relay_parent
|
||||
.get_mut(&hash_a)
|
||||
.unwrap()
|
||||
.validator_set
|
||||
.push(validator_1.into());
|
||||
@@ -230,7 +231,8 @@ fn receive_invalid_validator_index() {
|
||||
assert_ne!(peer_a, peer_b);
|
||||
|
||||
// validator 0 key pair
|
||||
let (mut state, signing_context, keystore, validator) = state_with_view(our_view![hash_a, hash_b], hash_a.clone());
|
||||
let (mut state, signing_context, keystore, validator) =
|
||||
state_with_view(our_view![hash_a, hash_b], hash_a.clone());
|
||||
|
||||
state.peer_views.insert(peer_b.clone(), view![hash_a]);
|
||||
|
||||
@@ -241,16 +243,16 @@ fn receive_invalid_validator_index() {
|
||||
&signing_context,
|
||||
ValidatorIndex(42),
|
||||
&validator,
|
||||
)).ok().flatten().expect("should be signed");
|
||||
))
|
||||
.ok()
|
||||
.flatten()
|
||||
.expect("should be signed");
|
||||
|
||||
let msg = BitfieldGossipMessage {
|
||||
relay_parent: hash_a.clone(),
|
||||
signed_availability: signed.clone(),
|
||||
};
|
||||
let msg =
|
||||
BitfieldGossipMessage { relay_parent: hash_a.clone(), signed_availability: signed.clone() };
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (mut ctx, mut handle) =
|
||||
make_subsystem_context::<BitfieldDistributionMessage, _>(pool);
|
||||
let (mut ctx, mut handle) = make_subsystem_context::<BitfieldDistributionMessage, _>(pool);
|
||||
|
||||
executor::block_on(async move {
|
||||
launch!(handle_network_msg(
|
||||
@@ -288,7 +290,8 @@ fn receive_duplicate_messages() {
|
||||
assert_ne!(peer_a, peer_b);
|
||||
|
||||
// validator 0 key pair
|
||||
let (mut state, signing_context, keystore, validator) = state_with_view(our_view![hash_a, hash_b], hash_a.clone());
|
||||
let (mut state, signing_context, keystore, validator) =
|
||||
state_with_view(our_view![hash_a, hash_b], hash_a.clone());
|
||||
|
||||
// create a signed message by validator 0
|
||||
let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]);
|
||||
@@ -298,7 +301,10 @@ fn receive_duplicate_messages() {
|
||||
&signing_context,
|
||||
ValidatorIndex(0),
|
||||
&validator,
|
||||
)).ok().flatten().expect("should be signed");
|
||||
))
|
||||
.ok()
|
||||
.flatten()
|
||||
.expect("should be signed");
|
||||
|
||||
let msg = BitfieldGossipMessage {
|
||||
relay_parent: hash_a.clone(),
|
||||
@@ -306,8 +312,7 @@ fn receive_duplicate_messages() {
|
||||
};
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (mut ctx, mut handle) =
|
||||
make_subsystem_context::<BitfieldDistributionMessage, _>(pool);
|
||||
let (mut ctx, mut handle) = make_subsystem_context::<BitfieldDistributionMessage, _>(pool);
|
||||
|
||||
executor::block_on(async move {
|
||||
// send a first message
|
||||
@@ -315,10 +320,7 @@ fn receive_duplicate_messages() {
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer_b.clone(),
|
||||
msg.clone().into_network_message(),
|
||||
),
|
||||
NetworkBridgeEvent::PeerMessage(peer_b.clone(), msg.clone().into_network_message(),),
|
||||
));
|
||||
|
||||
// none of our peers has any interest in any messages
|
||||
@@ -350,10 +352,7 @@ fn receive_duplicate_messages() {
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer_a.clone(),
|
||||
msg.clone().into_network_message(),
|
||||
),
|
||||
NetworkBridgeEvent::PeerMessage(peer_a.clone(), msg.clone().into_network_message(),),
|
||||
));
|
||||
|
||||
assert_matches!(
|
||||
@@ -371,10 +370,7 @@ fn receive_duplicate_messages() {
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer_b.clone(),
|
||||
msg.clone().into_network_message(),
|
||||
),
|
||||
NetworkBridgeEvent::PeerMessage(peer_b.clone(), msg.clone().into_network_message(),),
|
||||
));
|
||||
|
||||
assert_matches!(
|
||||
@@ -403,7 +399,8 @@ fn do_not_relay_message_twice() {
|
||||
assert_ne!(peer_a, peer_b);
|
||||
|
||||
// validator 0 key pair
|
||||
let (mut state, signing_context, keystore, validator) = state_with_view(our_view![hash], hash.clone());
|
||||
let (mut state, signing_context, keystore, validator) =
|
||||
state_with_view(our_view![hash], hash.clone());
|
||||
|
||||
// create a signed message by validator 0
|
||||
let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]);
|
||||
@@ -413,7 +410,10 @@ fn do_not_relay_message_twice() {
|
||||
&signing_context,
|
||||
ValidatorIndex(0),
|
||||
&validator,
|
||||
)).ok().flatten().expect("should be signed");
|
||||
))
|
||||
.ok()
|
||||
.flatten()
|
||||
.expect("should be signed");
|
||||
|
||||
state.peer_views.insert(peer_b.clone(), view![hash]);
|
||||
state.peer_views.insert(peer_a.clone(), view![hash]);
|
||||
@@ -424,13 +424,10 @@ fn do_not_relay_message_twice() {
|
||||
};
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (mut ctx, mut handle) =
|
||||
make_subsystem_context::<BitfieldDistributionMessage, _>(pool);
|
||||
let (mut ctx, mut handle) = make_subsystem_context::<BitfieldDistributionMessage, _>(pool);
|
||||
|
||||
executor::block_on(async move {
|
||||
let gossip_peers = HashSet::from_iter(vec![
|
||||
peer_a.clone(), peer_b.clone(),
|
||||
].into_iter());
|
||||
let gossip_peers = HashSet::from_iter(vec![peer_a.clone(), peer_b.clone()].into_iter());
|
||||
relay_message(
|
||||
&mut ctx,
|
||||
state.per_relay_parent.get_mut(&hash).unwrap(),
|
||||
@@ -438,7 +435,8 @@ fn do_not_relay_message_twice() {
|
||||
&mut state.peer_views,
|
||||
validator.clone(),
|
||||
msg.clone(),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
@@ -471,7 +469,8 @@ fn do_not_relay_message_twice() {
|
||||
&mut state.peer_views,
|
||||
validator.clone(),
|
||||
msg.clone(),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
@@ -504,7 +503,8 @@ fn changing_view() {
|
||||
assert_ne!(peer_a, peer_b);
|
||||
|
||||
// validator 0 key pair
|
||||
let (mut state, signing_context, keystore, validator) = state_with_view(our_view![hash_a, hash_b], hash_a.clone());
|
||||
let (mut state, signing_context, keystore, validator) =
|
||||
state_with_view(our_view![hash_a, hash_b], hash_a.clone());
|
||||
|
||||
// create a signed message by validator 0
|
||||
let payload = AvailabilityBitfield(bitvec![bitvec::order::Lsb0, u8; 1u8; 32]);
|
||||
@@ -514,7 +514,10 @@ fn changing_view() {
|
||||
&signing_context,
|
||||
ValidatorIndex(0),
|
||||
&validator,
|
||||
)).ok().flatten().expect("should be signed");
|
||||
))
|
||||
.ok()
|
||||
.flatten()
|
||||
.expect("should be signed");
|
||||
|
||||
let msg = BitfieldGossipMessage {
|
||||
relay_parent: hash_a.clone(),
|
||||
@@ -522,8 +525,7 @@ fn changing_view() {
|
||||
};
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (mut ctx, mut handle) =
|
||||
make_subsystem_context::<BitfieldDistributionMessage, _>(pool);
|
||||
let (mut ctx, mut handle) = make_subsystem_context::<BitfieldDistributionMessage, _>(pool);
|
||||
|
||||
executor::block_on(async move {
|
||||
launch!(handle_network_msg(
|
||||
@@ -548,10 +550,7 @@ fn changing_view() {
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer_b.clone(),
|
||||
msg.clone().into_network_message(),
|
||||
),
|
||||
NetworkBridgeEvent::PeerMessage(peer_b.clone(), msg.clone().into_network_message(),),
|
||||
));
|
||||
|
||||
// gossip to the overseer
|
||||
@@ -585,10 +584,7 @@ fn changing_view() {
|
||||
));
|
||||
|
||||
assert!(state.peer_views.contains_key(&peer_b));
|
||||
assert_eq!(
|
||||
state.peer_views.get(&peer_b).expect("Must contain value for peer B"),
|
||||
&view![]
|
||||
);
|
||||
assert_eq!(state.peer_views.get(&peer_b).expect("Must contain value for peer B"), &view![]);
|
||||
|
||||
// on rx of the same message, since we are not interested,
|
||||
// should give penalty
|
||||
@@ -596,10 +592,7 @@ fn changing_view() {
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer_b.clone(),
|
||||
msg.clone().into_network_message(),
|
||||
),
|
||||
NetworkBridgeEvent::PeerMessage(peer_b.clone(), msg.clone().into_network_message(),),
|
||||
));
|
||||
|
||||
// reputation change for peer B
|
||||
@@ -629,10 +622,7 @@ fn changing_view() {
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer_a.clone(),
|
||||
msg.clone().into_network_message(),
|
||||
),
|
||||
NetworkBridgeEvent::PeerMessage(peer_a.clone(), msg.clone().into_network_message(),),
|
||||
));
|
||||
|
||||
// reputation change for peer B
|
||||
@@ -645,7 +635,6 @@ fn changing_view() {
|
||||
assert_eq!(rep, COST_NOT_IN_VIEW)
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -673,7 +662,10 @@ fn do_not_send_message_back_to_origin() {
|
||||
&signing_context,
|
||||
ValidatorIndex(0),
|
||||
&validator,
|
||||
)).ok().flatten().expect("should be signed");
|
||||
))
|
||||
.ok()
|
||||
.flatten()
|
||||
.expect("should be signed");
|
||||
|
||||
state.peer_views.insert(peer_b.clone(), view![hash]);
|
||||
state.peer_views.insert(peer_a.clone(), view![hash]);
|
||||
@@ -684,8 +676,7 @@ fn do_not_send_message_back_to_origin() {
|
||||
};
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
let (mut ctx, mut handle) =
|
||||
make_subsystem_context::<BitfieldDistributionMessage, _>(pool);
|
||||
let (mut ctx, mut handle) = make_subsystem_context::<BitfieldDistributionMessage, _>(pool);
|
||||
|
||||
executor::block_on(async move {
|
||||
// send a first message
|
||||
@@ -693,10 +684,7 @@ fn do_not_send_message_back_to_origin() {
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer_b.clone(),
|
||||
msg.clone().into_network_message(),
|
||||
),
|
||||
NetworkBridgeEvent::PeerMessage(peer_b.clone(), msg.clone().into_network_message(),),
|
||||
));
|
||||
|
||||
assert_matches!(
|
||||
|
||||
@@ -19,48 +19,39 @@
|
||||
#![deny(unused_crate_dependencies)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
|
||||
use parity_scale_codec::{Encode, Decode};
|
||||
use futures::{prelude::*, stream::BoxStream};
|
||||
use parity_scale_codec::{Decode, Encode};
|
||||
use parking_lot::Mutex;
|
||||
use futures::prelude::*;
|
||||
use futures::stream::BoxStream;
|
||||
use polkadot_subsystem::messages::DisputeDistributionMessage;
|
||||
use sc_network::Event as NetworkEvent;
|
||||
use sp_consensus::SyncOracle;
|
||||
|
||||
use polkadot_overseer::gen::{
|
||||
Subsystem,
|
||||
OverseerError,
|
||||
};
|
||||
use polkadot_subsystem::{
|
||||
overseer,
|
||||
OverseerSignal,
|
||||
FromOverseer,
|
||||
SpawnedSubsystem,
|
||||
SubsystemContext,
|
||||
SubsystemSender,
|
||||
errors::{SubsystemError, SubsystemResult},
|
||||
ActivatedLeaf, ActiveLeavesUpdate,
|
||||
messages::{
|
||||
AllMessages, StatementDistributionMessage,
|
||||
NetworkBridgeMessage, CollatorProtocolMessage, NetworkBridgeEvent,
|
||||
},
|
||||
};
|
||||
use polkadot_primitives::v1::{Hash, BlockNumber};
|
||||
use polkadot_node_network_protocol::{
|
||||
PeerId, peer_set::PeerSet, View, v1 as protocol_v1, OurView, UnifiedReputationChange as Rep,
|
||||
ObservedRole,
|
||||
peer_set::PeerSet, v1 as protocol_v1, ObservedRole, OurView, PeerId,
|
||||
UnifiedReputationChange as Rep, View,
|
||||
};
|
||||
use polkadot_node_subsystem_util::metrics::{self, prometheus};
|
||||
use polkadot_overseer::gen::{OverseerError, Subsystem};
|
||||
use polkadot_primitives::v1::{BlockNumber, Hash};
|
||||
use polkadot_subsystem::{
|
||||
errors::{SubsystemError, SubsystemResult},
|
||||
messages::{
|
||||
AllMessages, CollatorProtocolMessage, NetworkBridgeEvent, NetworkBridgeMessage,
|
||||
StatementDistributionMessage,
|
||||
},
|
||||
overseer, ActivatedLeaf, ActiveLeavesUpdate, FromOverseer, OverseerSignal, SpawnedSubsystem,
|
||||
SubsystemContext, SubsystemSender,
|
||||
};
|
||||
|
||||
/// Peer set info for network initialization.
|
||||
///
|
||||
/// To be added to [`NetworkConfiguration::extra_sets`].
|
||||
pub use polkadot_node_network_protocol::peer_set::{peer_sets_info, IsAuthority};
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::collections::{HashMap, hash_map};
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
collections::{hash_map, HashMap, HashSet},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
mod validator_discovery;
|
||||
|
||||
@@ -68,7 +59,7 @@ mod validator_discovery;
|
||||
///
|
||||
/// Defines the `Network` trait with an implementation for an `Arc<NetworkService>`.
|
||||
mod network;
|
||||
use network::{Network, send_message};
|
||||
use network::{send_message, Network};
|
||||
|
||||
/// Request multiplexer for combining the multiple request sources into a single `Stream` of `AllMessages`.
|
||||
mod multiplexer;
|
||||
@@ -84,7 +75,6 @@ mod tests;
|
||||
/// We use the same limit to compute the view sent to peers locally.
|
||||
const MAX_VIEW_HEADS: usize = 5;
|
||||
|
||||
|
||||
const MALFORMED_MESSAGE_COST: Rep = Rep::CostMajor("Malformed Network-bridge message");
|
||||
const UNCONNECTED_PEERSET_COST: Rep = Rep::CostMinor("Message sent to un-connected peer-set");
|
||||
const MALFORMED_VIEW_COST: Rep = Rep::CostMajor("Malformed view");
|
||||
@@ -99,36 +89,41 @@ pub struct Metrics(Option<MetricsInner>);
|
||||
|
||||
impl Metrics {
|
||||
fn on_peer_connected(&self, peer_set: PeerSet) {
|
||||
self.0.as_ref().map(|metrics| metrics
|
||||
.connected_events
|
||||
.with_label_values(&[peer_set.get_protocol_name_static()])
|
||||
.inc()
|
||||
);
|
||||
self.0.as_ref().map(|metrics| {
|
||||
metrics
|
||||
.connected_events
|
||||
.with_label_values(&[peer_set.get_protocol_name_static()])
|
||||
.inc()
|
||||
});
|
||||
}
|
||||
|
||||
fn on_peer_disconnected(&self, peer_set: PeerSet) {
|
||||
self.0.as_ref().map(|metrics| metrics
|
||||
.disconnected_events
|
||||
.with_label_values(&[peer_set.get_protocol_name_static()])
|
||||
.inc()
|
||||
);
|
||||
self.0.as_ref().map(|metrics| {
|
||||
metrics
|
||||
.disconnected_events
|
||||
.with_label_values(&[peer_set.get_protocol_name_static()])
|
||||
.inc()
|
||||
});
|
||||
}
|
||||
|
||||
fn note_peer_count(&self, peer_set: PeerSet, count: usize) {
|
||||
self.0.as_ref().map(|metrics| metrics
|
||||
.peer_count
|
||||
.with_label_values(&[peer_set.get_protocol_name_static()])
|
||||
.set(count as u64)
|
||||
);
|
||||
self.0.as_ref().map(|metrics| {
|
||||
metrics
|
||||
.peer_count
|
||||
.with_label_values(&[peer_set.get_protocol_name_static()])
|
||||
.set(count as u64)
|
||||
});
|
||||
}
|
||||
|
||||
fn on_notification_received(&self, peer_set: PeerSet, size: usize) {
|
||||
if let Some(metrics) = self.0.as_ref() {
|
||||
metrics.notifications_received
|
||||
metrics
|
||||
.notifications_received
|
||||
.with_label_values(&[peer_set.get_protocol_name_static()])
|
||||
.inc();
|
||||
|
||||
metrics.bytes_received
|
||||
metrics
|
||||
.bytes_received
|
||||
.with_label_values(&[peer_set.get_protocol_name_static()])
|
||||
.inc_by(size as u64);
|
||||
}
|
||||
@@ -136,22 +131,25 @@ impl Metrics {
|
||||
|
||||
fn on_notification_sent(&self, peer_set: PeerSet, size: usize, to_peers: usize) {
|
||||
if let Some(metrics) = self.0.as_ref() {
|
||||
metrics.notifications_sent
|
||||
metrics
|
||||
.notifications_sent
|
||||
.with_label_values(&[peer_set.get_protocol_name_static()])
|
||||
.inc_by(to_peers as u64);
|
||||
|
||||
metrics.bytes_sent
|
||||
metrics
|
||||
.bytes_sent
|
||||
.with_label_values(&[peer_set.get_protocol_name_static()])
|
||||
.inc_by((size * to_peers) as u64);
|
||||
}
|
||||
}
|
||||
|
||||
fn note_desired_peer_count(&self, peer_set: PeerSet, size: usize) {
|
||||
self.0.as_ref().map(|metrics| metrics
|
||||
.desired_peer_count
|
||||
.with_label_values(&[peer_set.get_protocol_name_static()])
|
||||
.set(size as u64)
|
||||
);
|
||||
self.0.as_ref().map(|metrics| {
|
||||
metrics
|
||||
.desired_peer_count
|
||||
.with_label_values(&[peer_set.get_protocol_name_static()])
|
||||
.set(size as u64)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,9 +168,9 @@ struct MetricsInner {
|
||||
}
|
||||
|
||||
impl metrics::Metrics for Metrics {
|
||||
fn try_register(registry: &prometheus::Registry)
|
||||
-> std::result::Result<Self, prometheus::PrometheusError>
|
||||
{
|
||||
fn try_register(
|
||||
registry: &prometheus::Registry,
|
||||
) -> std::result::Result<Self, prometheus::PrometheusError> {
|
||||
let metrics = MetricsInner {
|
||||
peer_count: prometheus::register(
|
||||
prometheus::GaugeVec::new(
|
||||
@@ -306,10 +304,11 @@ impl<N, AD> NetworkBridge<N, AD> {
|
||||
}
|
||||
|
||||
impl<Net, AD, Context> Subsystem<Context, SubsystemError> for NetworkBridge<Net, AD>
|
||||
where
|
||||
Net: Network + Sync,
|
||||
AD: validator_discovery::AuthorityDiscovery + Clone,
|
||||
Context: SubsystemContext<Message = NetworkBridgeMessage> + overseer::SubsystemContext<Message = NetworkBridgeMessage>,
|
||||
where
|
||||
Net: Network + Sync,
|
||||
AD: validator_discovery::AuthorityDiscovery + Clone,
|
||||
Context: SubsystemContext<Message = NetworkBridgeMessage>
|
||||
+ overseer::SubsystemContext<Message = NetworkBridgeMessage>,
|
||||
{
|
||||
fn start(mut self, ctx: Context) -> SpawnedSubsystem {
|
||||
// The stream of networking events has to be created at initialization, otherwise the
|
||||
@@ -319,14 +318,9 @@ impl<Net, AD, Context> Subsystem<Context, SubsystemError> for NetworkBridge<Net,
|
||||
// Swallow error because failure is fatal to the node and we log with more precision
|
||||
// within `run_network`.
|
||||
let future = run_network(self, ctx, network_stream)
|
||||
.map_err(|e| {
|
||||
SubsystemError::with_origin("network-bridge", e)
|
||||
})
|
||||
.map_err(|e| SubsystemError::with_origin("network-bridge", e))
|
||||
.boxed();
|
||||
SpawnedSubsystem {
|
||||
name: "network-bridge-subsystem",
|
||||
future,
|
||||
}
|
||||
SpawnedSubsystem { name: "network-bridge-subsystem", future }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -882,7 +876,8 @@ async fn run_network<N, AD, Context>(
|
||||
where
|
||||
N: Network,
|
||||
AD: validator_discovery::AuthorityDiscovery + Clone,
|
||||
Context: SubsystemContext<Message=NetworkBridgeMessage> + overseer::SubsystemContext<Message=NetworkBridgeMessage>,
|
||||
Context: SubsystemContext<Message = NetworkBridgeMessage>
|
||||
+ overseer::SubsystemContext<Message = NetworkBridgeMessage>,
|
||||
{
|
||||
let shared = Shared::default();
|
||||
|
||||
@@ -902,7 +897,7 @@ where
|
||||
.get_dispute_sending()
|
||||
.expect("Gets initialized, must be `Some` on startup. qed.");
|
||||
|
||||
let (remote, network_event_handler) = handle_network_messages::<>(
|
||||
let (remote, network_event_handler) = handle_network_messages(
|
||||
ctx.sender().clone(),
|
||||
network_service.clone(),
|
||||
network_stream,
|
||||
@@ -910,16 +905,15 @@ where
|
||||
request_multiplexer,
|
||||
metrics.clone(),
|
||||
shared.clone(),
|
||||
).remote_handle();
|
||||
)
|
||||
.remote_handle();
|
||||
|
||||
ctx.spawn("network-bridge-network-worker", Box::pin(remote))?;
|
||||
|
||||
ctx.send_message(
|
||||
DisputeDistributionMessage::DisputeSendingReceiver(dispute_receiver)
|
||||
).await;
|
||||
ctx.send_message(
|
||||
StatementDistributionMessage::StatementFetchingReceiver(statement_receiver)
|
||||
).await;
|
||||
ctx.send_message(DisputeDistributionMessage::DisputeSendingReceiver(dispute_receiver))
|
||||
.await;
|
||||
ctx.send_message(StatementDistributionMessage::StatementFetchingReceiver(statement_receiver))
|
||||
.await;
|
||||
|
||||
let subsystem_event_handler = handle_subsystem_messages(
|
||||
ctx,
|
||||
@@ -949,38 +943,34 @@ where
|
||||
"Received SubsystemError from overseer: {:?}",
|
||||
err
|
||||
)))
|
||||
}
|
||||
},
|
||||
Err(UnexpectedAbort::EventStreamConcluded) => {
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
"Shutting down Network Bridge: underlying request stream concluded"
|
||||
);
|
||||
Err(SubsystemError::Context(
|
||||
"Incoming network event stream concluded.".to_string(),
|
||||
))
|
||||
}
|
||||
Err(SubsystemError::Context("Incoming network event stream concluded.".to_string()))
|
||||
},
|
||||
Err(UnexpectedAbort::RequestStreamConcluded) => {
|
||||
tracing::info!(
|
||||
target: LOG_TARGET,
|
||||
"Shutting down Network Bridge: underlying request stream concluded"
|
||||
);
|
||||
Err(SubsystemError::Context(
|
||||
"Incoming network request stream concluded".to_string(),
|
||||
))
|
||||
}
|
||||
Err(SubsystemError::Context("Incoming network request stream concluded".to_string()))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn construct_view(live_heads: impl DoubleEndedIterator<Item = Hash>, finalized_number: BlockNumber) -> View {
|
||||
View::new(
|
||||
live_heads.take(MAX_VIEW_HEADS),
|
||||
finalized_number,
|
||||
)
|
||||
fn construct_view(
|
||||
live_heads: impl DoubleEndedIterator<Item = Hash>,
|
||||
finalized_number: BlockNumber,
|
||||
) -> View {
|
||||
View::new(live_heads.take(MAX_VIEW_HEADS), finalized_number)
|
||||
}
|
||||
|
||||
fn update_our_view(
|
||||
net: &mut impl Network,
|
||||
ctx: &mut impl SubsystemContext<Message=NetworkBridgeMessage, AllMessages=AllMessages>,
|
||||
ctx: &mut impl SubsystemContext<Message = NetworkBridgeMessage, AllMessages = AllMessages>,
|
||||
live_heads: &[ActivatedLeaf],
|
||||
shared: &Shared,
|
||||
finalized_number: BlockNumber,
|
||||
@@ -997,17 +987,14 @@ fn update_our_view(
|
||||
// If this is the first view update since becoming active, but our view is empty,
|
||||
// there is no need to send anything.
|
||||
match shared.local_view {
|
||||
Some(ref v) if v.check_heads_eq(&new_view) => {
|
||||
return;
|
||||
}
|
||||
Some(ref v) if v.check_heads_eq(&new_view) => return,
|
||||
None if live_heads.is_empty() => {
|
||||
shared.local_view = Some(new_view);
|
||||
return;
|
||||
}
|
||||
return
|
||||
},
|
||||
_ => {
|
||||
shared.local_view = Some(new_view.clone());
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
(
|
||||
@@ -1023,12 +1010,7 @@ fn update_our_view(
|
||||
metrics,
|
||||
);
|
||||
|
||||
send_collation_message(
|
||||
net,
|
||||
collation_peers,
|
||||
WireMessage::ViewUpdate(new_view),
|
||||
metrics,
|
||||
);
|
||||
send_collation_message(net, collation_peers, WireMessage::ViewUpdate(new_view), metrics);
|
||||
|
||||
let our_view = OurView::new(
|
||||
live_heads.iter().take(MAX_VIEW_HEADS).cloned().map(|a| (a.hash, a.span)),
|
||||
@@ -1056,9 +1038,7 @@ fn handle_peer_messages<M>(
|
||||
metrics: &Metrics,
|
||||
) -> (Vec<NetworkBridgeEvent<M>>, Vec<Rep>) {
|
||||
let peer_data = match peers.get_mut(&peer) {
|
||||
None => {
|
||||
return (Vec::new(), vec![UNCONNECTED_PEERSET_COST]);
|
||||
},
|
||||
None => return (Vec::new(), vec![UNCONNECTED_PEERSET_COST]),
|
||||
Some(d) => d,
|
||||
};
|
||||
|
||||
@@ -1083,15 +1063,11 @@ fn handle_peer_messages<M>(
|
||||
} else {
|
||||
peer_data.view = new_view;
|
||||
|
||||
NetworkBridgeEvent::PeerViewChange(
|
||||
peer.clone(),
|
||||
peer_data.view.clone(),
|
||||
)
|
||||
NetworkBridgeEvent::PeerViewChange(peer.clone(), peer_data.view.clone())
|
||||
}
|
||||
}
|
||||
WireMessage::ProtocolMessage(message) => {
|
||||
NetworkBridgeEvent::PeerMessage(peer.clone(), message)
|
||||
}
|
||||
},
|
||||
WireMessage::ProtocolMessage(message) =>
|
||||
NetworkBridgeEvent::PeerMessage(peer.clone(), message),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1116,24 +1092,23 @@ fn send_collation_message(
|
||||
send_message(net, peers, PeerSet::Collation, message, metrics)
|
||||
}
|
||||
|
||||
|
||||
async fn dispatch_validation_event_to_all(
|
||||
event: NetworkBridgeEvent<protocol_v1::ValidationProtocol>,
|
||||
ctx: &mut impl SubsystemSender
|
||||
ctx: &mut impl SubsystemSender,
|
||||
) {
|
||||
dispatch_validation_events_to_all(std::iter::once(event), ctx).await
|
||||
}
|
||||
|
||||
async fn dispatch_collation_event_to_all(
|
||||
event: NetworkBridgeEvent<protocol_v1::CollationProtocol>,
|
||||
ctx: &mut impl SubsystemSender
|
||||
ctx: &mut impl SubsystemSender,
|
||||
) {
|
||||
dispatch_collation_events_to_all(std::iter::once(event), ctx).await
|
||||
}
|
||||
|
||||
fn dispatch_validation_event_to_all_unbounded(
|
||||
event: NetworkBridgeEvent<protocol_v1::ValidationProtocol>,
|
||||
ctx: &mut impl SubsystemSender
|
||||
ctx: &mut impl SubsystemSender,
|
||||
) {
|
||||
for msg in AllMessages::dispatch_iter(event) {
|
||||
ctx.send_unbounded_message(msg);
|
||||
@@ -1142,36 +1117,30 @@ fn dispatch_validation_event_to_all_unbounded(
|
||||
|
||||
fn dispatch_collation_event_to_all_unbounded(
|
||||
event: NetworkBridgeEvent<protocol_v1::CollationProtocol>,
|
||||
ctx: &mut impl SubsystemSender
|
||||
ctx: &mut impl SubsystemSender,
|
||||
) {
|
||||
if let Some(msg) = event.focus().ok().map(CollatorProtocolMessage::NetworkBridgeUpdateV1) {
|
||||
ctx.send_unbounded_message(msg.into());
|
||||
}
|
||||
}
|
||||
|
||||
async fn dispatch_validation_events_to_all<I>(
|
||||
events: I,
|
||||
ctx: &mut impl SubsystemSender
|
||||
)
|
||||
where
|
||||
I: IntoIterator<Item = NetworkBridgeEvent<protocol_v1::ValidationProtocol>>,
|
||||
I::IntoIter: Send,
|
||||
async fn dispatch_validation_events_to_all<I>(events: I, ctx: &mut impl SubsystemSender)
|
||||
where
|
||||
I: IntoIterator<Item = NetworkBridgeEvent<protocol_v1::ValidationProtocol>>,
|
||||
I::IntoIter: Send,
|
||||
{
|
||||
ctx.send_messages(events.into_iter().flat_map(AllMessages::dispatch_iter)).await
|
||||
}
|
||||
|
||||
async fn dispatch_collation_events_to_all<I>(
|
||||
events: I,
|
||||
ctx: &mut impl SubsystemSender
|
||||
)
|
||||
where
|
||||
I: IntoIterator<Item = NetworkBridgeEvent<protocol_v1::CollationProtocol>>,
|
||||
I::IntoIter: Send,
|
||||
async fn dispatch_collation_events_to_all<I>(events: I, ctx: &mut impl SubsystemSender)
|
||||
where
|
||||
I: IntoIterator<Item = NetworkBridgeEvent<protocol_v1::CollationProtocol>>,
|
||||
I::IntoIter: Send,
|
||||
{
|
||||
let messages_for = |event: NetworkBridgeEvent<protocol_v1::CollationProtocol>| {
|
||||
event.focus().ok().map(|m| AllMessages::CollatorProtocol(
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(m)
|
||||
))
|
||||
event.focus().ok().map(|m| {
|
||||
AllMessages::CollatorProtocol(CollatorProtocolMessage::NetworkBridgeUpdateV1(m))
|
||||
})
|
||||
};
|
||||
|
||||
ctx.send_messages(events.into_iter().flat_map(messages_for)).await
|
||||
|
||||
@@ -14,18 +14,18 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::pin::Pin;
|
||||
use std::unreachable;
|
||||
use std::{pin::Pin, unreachable};
|
||||
|
||||
use futures::channel::mpsc;
|
||||
use futures::stream::{FusedStream, Stream};
|
||||
use futures::task::{Context, Poll};
|
||||
use futures::{
|
||||
channel::mpsc,
|
||||
stream::{FusedStream, Stream},
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use parity_scale_codec::{Decode, Error as DecodingError};
|
||||
|
||||
use sc_network::config as network;
|
||||
use sc_network::PeerId;
|
||||
use sc_network::{config as network, PeerId};
|
||||
|
||||
use polkadot_node_network_protocol::request_response::{
|
||||
request::IncomingRequest, v1, Protocol, RequestResponseConfig,
|
||||
@@ -72,33 +72,23 @@ impl RequestMultiplexer {
|
||||
|
||||
// Ok this code is ugly as hell, it is also a hack, see https://github.com/paritytech/polkadot/issues/2842.
|
||||
// But it works and is executed on startup so, if anything is wrong here it will be noticed immediately.
|
||||
let index = receivers.iter().enumerate().find_map(|(i, (p, _))|
|
||||
if let Protocol::StatementFetching = p {
|
||||
Some(i)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
).expect("Statement fetching must be registered. qed.");
|
||||
let index = receivers
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(
|
||||
|(i, (p, _))| if let Protocol::StatementFetching = p { Some(i) } else { None },
|
||||
)
|
||||
.expect("Statement fetching must be registered. qed.");
|
||||
let statement_fetching = Some(receivers.remove(index).1);
|
||||
|
||||
let index = receivers.iter().enumerate().find_map(|(i, (p, _))|
|
||||
if let Protocol::DisputeSending = p {
|
||||
Some(i)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
).expect("Dispute sending must be registered. qed.");
|
||||
let index = receivers
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(i, (p, _))| if let Protocol::DisputeSending = p { Some(i) } else { None })
|
||||
.expect("Dispute sending must be registered. qed.");
|
||||
let dispute_sending = Some(receivers.remove(index).1);
|
||||
|
||||
(
|
||||
Self {
|
||||
receivers,
|
||||
statement_fetching,
|
||||
dispute_sending,
|
||||
next_poll: 0,
|
||||
},
|
||||
cfgs,
|
||||
)
|
||||
(Self { receivers, statement_fetching, dispute_sending, next_poll: 0 }, cfgs)
|
||||
}
|
||||
|
||||
/// Get the receiver for handling statement fetching requests.
|
||||
@@ -132,7 +122,7 @@ impl Stream for RequestMultiplexer {
|
||||
// Avoid panic:
|
||||
if rx.is_terminated() {
|
||||
// Early return, we don't want to update next_poll.
|
||||
return Poll::Ready(None);
|
||||
return Poll::Ready(None)
|
||||
}
|
||||
i += 1;
|
||||
count -= 1;
|
||||
@@ -142,8 +132,8 @@ impl Stream for RequestMultiplexer {
|
||||
Poll::Ready(None) => return Poll::Ready(None),
|
||||
Poll::Ready(Some(v)) => {
|
||||
result = Poll::Ready(Some(multiplex_single(*p, v)));
|
||||
break;
|
||||
}
|
||||
break
|
||||
},
|
||||
}
|
||||
}
|
||||
self.next_poll = i;
|
||||
@@ -155,7 +145,7 @@ impl FusedStream for RequestMultiplexer {
|
||||
fn is_terminated(&self) -> bool {
|
||||
let len = self.receivers.len();
|
||||
if len == 0 {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
let (_, rx) = &self.receivers[self.next_poll % len];
|
||||
rx.is_terminated()
|
||||
@@ -165,11 +155,7 @@ impl FusedStream for RequestMultiplexer {
|
||||
/// Convert a single raw incoming request into a `MultiplexMessage`.
|
||||
fn multiplex_single(
|
||||
p: Protocol,
|
||||
network::IncomingRequest {
|
||||
payload,
|
||||
peer,
|
||||
pending_response,
|
||||
}: network::IncomingRequest,
|
||||
network::IncomingRequest { payload, peer, pending_response }: network::IncomingRequest,
|
||||
) -> Result<AllMessages, RequestMultiplexError> {
|
||||
let r = match p {
|
||||
Protocol::ChunkFetching => AllMessages::from(IncomingRequest::new(
|
||||
@@ -194,10 +180,10 @@ fn multiplex_single(
|
||||
)),
|
||||
Protocol::StatementFetching => {
|
||||
unreachable!("Statement fetching requests are handled directly. qed.");
|
||||
}
|
||||
},
|
||||
Protocol::DisputeSending => {
|
||||
unreachable!("Dispute sending request are handled directly. qed.");
|
||||
}
|
||||
},
|
||||
};
|
||||
Ok(r)
|
||||
}
|
||||
@@ -211,8 +197,7 @@ fn decode_with_peer<Req: Decode>(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use futures::prelude::*;
|
||||
use futures::stream::FusedStream;
|
||||
use futures::{prelude::*, stream::FusedStream};
|
||||
|
||||
use super::RequestMultiplexer;
|
||||
#[test]
|
||||
|
||||
@@ -14,23 +14,21 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use std::{borrow::Cow, collections::HashSet, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use futures::prelude::*;
|
||||
use futures::stream::BoxStream;
|
||||
use futures::{prelude::*, stream::BoxStream};
|
||||
|
||||
use parity_scale_codec::Encode;
|
||||
|
||||
use sc_network::Event as NetworkEvent;
|
||||
use sc_network::{IfDisconnected, NetworkService, OutboundFailure, RequestFailure};
|
||||
use sc_network::{config::parse_addr, multiaddr::Multiaddr};
|
||||
use sc_network::{
|
||||
config::parse_addr, multiaddr::Multiaddr, Event as NetworkEvent, IfDisconnected,
|
||||
NetworkService, OutboundFailure, RequestFailure,
|
||||
};
|
||||
|
||||
use polkadot_node_network_protocol::{
|
||||
peer_set::PeerSet,
|
||||
request_response::{OutgoingRequest, Requests, Recipient},
|
||||
request_response::{OutgoingRequest, Recipient, Requests},
|
||||
PeerId, UnifiedReputationChange as Rep,
|
||||
};
|
||||
use polkadot_primitives::v1::{AuthorityDiscoveryId, Block, Hash};
|
||||
@@ -50,8 +48,7 @@ pub(crate) fn send_message<M>(
|
||||
peer_set: PeerSet,
|
||||
message: M,
|
||||
metrics: &super::Metrics,
|
||||
)
|
||||
where
|
||||
) where
|
||||
M: Encode + Clone,
|
||||
{
|
||||
let message = {
|
||||
@@ -112,12 +109,7 @@ pub trait Network: Clone + Send + 'static {
|
||||
fn disconnect_peer(&self, who: PeerId, peer_set: PeerSet);
|
||||
|
||||
/// Write a notification to a peer on the given peer-set's protocol.
|
||||
fn write_notification(
|
||||
&self,
|
||||
who: PeerId,
|
||||
peer_set: PeerSet,
|
||||
message: Vec<u8>,
|
||||
);
|
||||
fn write_notification(&self, who: PeerId, peer_set: PeerSet, message: Vec<u8>);
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -170,38 +162,29 @@ impl Network for Arc<NetworkService<Block, Hash>> {
|
||||
req: Requests,
|
||||
if_disconnected: IfDisconnected,
|
||||
) {
|
||||
let (
|
||||
protocol,
|
||||
OutgoingRequest {
|
||||
peer,
|
||||
payload,
|
||||
pending_response,
|
||||
},
|
||||
) = req.encode_request();
|
||||
let (protocol, OutgoingRequest { peer, payload, pending_response }) = req.encode_request();
|
||||
|
||||
let peer_id = match peer {
|
||||
Recipient::Peer(peer_id) => Some(peer_id),
|
||||
Recipient::Peer(peer_id) => Some(peer_id),
|
||||
Recipient::Authority(authority) => {
|
||||
let mut found_peer_id = None;
|
||||
// Note: `get_addresses_by_authority_id` searched in a cache, and it thus expected
|
||||
// to be very quick.
|
||||
for addr in authority_discovery
|
||||
.get_addresses_by_authority_id(authority).await
|
||||
.into_iter().flat_map(|list| list.into_iter())
|
||||
.get_addresses_by_authority_id(authority)
|
||||
.await
|
||||
.into_iter()
|
||||
.flat_map(|list| list.into_iter())
|
||||
{
|
||||
let (peer_id, addr) = match parse_addr(addr) {
|
||||
Ok(v) => v,
|
||||
Err(_) => continue,
|
||||
};
|
||||
NetworkService::add_known_address(
|
||||
&*self,
|
||||
peer_id.clone(),
|
||||
addr,
|
||||
);
|
||||
NetworkService::add_known_address(&*self, peer_id.clone(), addr);
|
||||
found_peer_id = Some(peer_id);
|
||||
}
|
||||
found_peer_id
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let peer_id = match peer_id {
|
||||
@@ -214,10 +197,10 @@ impl Network for Arc<NetworkService<Block, Hash>> {
|
||||
target: LOG_TARGET,
|
||||
"Sending failed request response failed."
|
||||
),
|
||||
Ok(_) => {}
|
||||
Ok(_) => {},
|
||||
}
|
||||
return;
|
||||
}
|
||||
return
|
||||
},
|
||||
Some(peer_id) => peer_id,
|
||||
};
|
||||
|
||||
@@ -240,7 +223,8 @@ pub async fn get_peer_id_by_authority_id<AD: AuthorityDiscovery>(
|
||||
// Note: `get_addresses_by_authority_id` searched in a cache, and it thus expected
|
||||
// to be very quick.
|
||||
authority_discovery
|
||||
.get_addresses_by_authority_id(authority).await
|
||||
.get_addresses_by_authority_id(authority)
|
||||
.await
|
||||
.into_iter()
|
||||
.flat_map(|list| list.into_iter())
|
||||
.find_map(|addr| parse_addr(addr).ok().map(|(p, _)| p))
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -25,9 +25,9 @@ use futures::channel::oneshot;
|
||||
|
||||
use sc_network::multiaddr::Multiaddr;
|
||||
|
||||
use polkadot_primitives::v1::AuthorityDiscoveryId;
|
||||
use polkadot_node_network_protocol::peer_set::{PeerSet, PerPeerSet};
|
||||
pub use polkadot_node_network_protocol::authority_discovery::AuthorityDiscovery;
|
||||
use polkadot_node_network_protocol::peer_set::{PeerSet, PerPeerSet};
|
||||
use polkadot_primitives::v1::AuthorityDiscoveryId;
|
||||
|
||||
const LOG_TARGET: &str = "parachain::validator-discovery";
|
||||
|
||||
@@ -44,10 +44,7 @@ struct StatePerPeerSet {
|
||||
|
||||
impl<N: Network, AD: AuthorityDiscovery> Service<N, AD> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: Default::default(),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
Self { state: Default::default(), _phantom: PhantomData }
|
||||
}
|
||||
|
||||
/// On a new connection request, a peer set update will be issued.
|
||||
@@ -70,24 +67,27 @@ impl<N: Network, AD: AuthorityDiscovery> Service<N, AD> {
|
||||
let mut newly_requested = HashSet::new();
|
||||
let requested = validator_ids.len();
|
||||
for authority in validator_ids.into_iter() {
|
||||
let result = authority_discovery_service.get_addresses_by_authority_id(authority.clone()).await;
|
||||
let result = authority_discovery_service
|
||||
.get_addresses_by_authority_id(authority.clone())
|
||||
.await;
|
||||
if let Some(addresses) = result {
|
||||
newly_requested.extend(addresses);
|
||||
} else {
|
||||
failed_to_resolve += 1;
|
||||
tracing::debug!(target: LOG_TARGET, "Authority Discovery couldn't resolve {:?}", authority);
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Authority Discovery couldn't resolve {:?}",
|
||||
authority
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let state = &mut self.state[peer_set];
|
||||
// clean up revoked requests
|
||||
let multiaddr_to_remove: HashSet<_> = state.previously_requested
|
||||
.difference(&newly_requested)
|
||||
.cloned()
|
||||
.collect();
|
||||
let multiaddr_to_add: HashSet<_> = newly_requested.difference(&state.previously_requested)
|
||||
.cloned()
|
||||
.collect();
|
||||
let multiaddr_to_remove: HashSet<_> =
|
||||
state.previously_requested.difference(&newly_requested).cloned().collect();
|
||||
let multiaddr_to_add: HashSet<_> =
|
||||
newly_requested.difference(&state.previously_requested).cloned().collect();
|
||||
state.previously_requested = newly_requested;
|
||||
|
||||
tracing::debug!(
|
||||
@@ -101,17 +101,16 @@ impl<N: Network, AD: AuthorityDiscovery> Service<N, AD> {
|
||||
);
|
||||
// ask the network to connect to these nodes and not disconnect
|
||||
// from them until removed from the set
|
||||
if let Err(e) = network_service.add_to_peers_set(
|
||||
peer_set.into_protocol_name(),
|
||||
multiaddr_to_add,
|
||||
).await {
|
||||
if let Err(e) = network_service
|
||||
.add_to_peers_set(peer_set.into_protocol_name(), multiaddr_to_add)
|
||||
.await
|
||||
{
|
||||
tracing::warn!(target: LOG_TARGET, err = ?e, "AuthorityDiscoveryService returned an invalid multiaddress");
|
||||
}
|
||||
// the addresses are known to be valid
|
||||
let _ = network_service.remove_from_peers_set(
|
||||
peer_set.into_protocol_name(),
|
||||
multiaddr_to_remove
|
||||
).await;
|
||||
let _ = network_service
|
||||
.remove_from_peers_set(peer_set.into_protocol_name(), multiaddr_to_remove)
|
||||
.await;
|
||||
|
||||
let _ = failed.send(failed_to_resolve);
|
||||
|
||||
@@ -124,12 +123,12 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::network::Network;
|
||||
|
||||
use std::{borrow::Cow, collections::HashMap};
|
||||
use futures::stream::BoxStream;
|
||||
use async_trait::async_trait;
|
||||
use futures::stream::BoxStream;
|
||||
use polkadot_node_network_protocol::{request_response::request::Requests, PeerId};
|
||||
use sc_network::{Event as NetworkEvent, IfDisconnected};
|
||||
use sp_keyring::Sr25519Keyring;
|
||||
use polkadot_node_network_protocol::{PeerId, request_response::request::Requests};
|
||||
use std::{borrow::Cow, collections::HashMap};
|
||||
|
||||
fn new_service() -> Service<TestNetwork, TestAuthorityDiscovery> {
|
||||
Service::new()
|
||||
@@ -156,13 +155,8 @@ mod tests {
|
||||
let authorities = known_authorities();
|
||||
let multiaddr = known_multiaddr();
|
||||
Self {
|
||||
by_authority_id: authorities.iter()
|
||||
.cloned()
|
||||
.zip(multiaddr.into_iter())
|
||||
.collect(),
|
||||
by_peer_id: peer_ids.into_iter()
|
||||
.zip(authorities.into_iter())
|
||||
.collect(),
|
||||
by_authority_id: authorities.iter().cloned().zip(multiaddr.into_iter()).collect(),
|
||||
by_peer_id: peer_ids.into_iter().zip(authorities.into_iter()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,17 +167,30 @@ mod tests {
|
||||
panic!()
|
||||
}
|
||||
|
||||
async fn add_to_peers_set(&mut self, _protocol: Cow<'static, str>, multiaddresses: HashSet<Multiaddr>) -> Result<(), String> {
|
||||
async fn add_to_peers_set(
|
||||
&mut self,
|
||||
_protocol: Cow<'static, str>,
|
||||
multiaddresses: HashSet<Multiaddr>,
|
||||
) -> Result<(), String> {
|
||||
self.peers_set.extend(multiaddresses.into_iter());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_from_peers_set(&mut self, _protocol: Cow<'static, str>, multiaddresses: HashSet<Multiaddr>) -> Result<(), String> {
|
||||
async fn remove_from_peers_set(
|
||||
&mut self,
|
||||
_protocol: Cow<'static, str>,
|
||||
multiaddresses: HashSet<Multiaddr>,
|
||||
) -> Result<(), String> {
|
||||
self.peers_set.retain(|elem| !multiaddresses.contains(elem));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn start_request<AD: AuthorityDiscovery>(&self, _: &mut AD, _: Requests, _: IfDisconnected) {
|
||||
async fn start_request<AD: AuthorityDiscovery>(
|
||||
&self,
|
||||
_: &mut AD,
|
||||
_: Requests,
|
||||
_: IfDisconnected,
|
||||
) {
|
||||
}
|
||||
|
||||
fn report_peer(&self, _: PeerId, _: crate::Rep) {
|
||||
@@ -194,33 +201,33 @@ mod tests {
|
||||
panic!()
|
||||
}
|
||||
|
||||
fn write_notification(
|
||||
&self,
|
||||
_: PeerId,
|
||||
_: PeerSet,
|
||||
_: Vec<u8>,
|
||||
) {
|
||||
fn write_notification(&self, _: PeerId, _: PeerSet, _: Vec<u8>) {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AuthorityDiscovery for TestAuthorityDiscovery {
|
||||
async fn get_addresses_by_authority_id(&mut self, authority: AuthorityDiscoveryId) -> Option<Vec<Multiaddr>> {
|
||||
async fn get_addresses_by_authority_id(
|
||||
&mut self,
|
||||
authority: AuthorityDiscoveryId,
|
||||
) -> Option<Vec<Multiaddr>> {
|
||||
self.by_authority_id.get(&authority).cloned().map(|addr| vec![addr])
|
||||
}
|
||||
|
||||
async fn get_authority_id_by_peer_id(&mut self, peer_id: PeerId) -> Option<AuthorityDiscoveryId> {
|
||||
async fn get_authority_id_by_peer_id(
|
||||
&mut self,
|
||||
peer_id: PeerId,
|
||||
) -> Option<AuthorityDiscoveryId> {
|
||||
self.by_peer_id.get(&peer_id).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
fn known_authorities() -> Vec<AuthorityDiscoveryId> {
|
||||
[
|
||||
Sr25519Keyring::Alice,
|
||||
Sr25519Keyring::Bob,
|
||||
Sr25519Keyring::Charlie,
|
||||
].iter().map(|k| k.public().into()).collect()
|
||||
[Sr25519Keyring::Alice, Sr25519Keyring::Bob, Sr25519Keyring::Charlie]
|
||||
.iter()
|
||||
.map(|k| k.public().into())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn known_peer_ids() -> Vec<PeerId> {
|
||||
@@ -245,26 +252,20 @@ mod tests {
|
||||
|
||||
futures::executor::block_on(async move {
|
||||
let (failed, _) = oneshot::channel();
|
||||
let (ns, ads) = service.on_request(
|
||||
vec![authority_ids[0].clone()],
|
||||
PeerSet::Validation,
|
||||
failed,
|
||||
ns,
|
||||
ads,
|
||||
).await;
|
||||
let (ns, ads) = service
|
||||
.on_request(vec![authority_ids[0].clone()], PeerSet::Validation, failed, ns, ads)
|
||||
.await;
|
||||
|
||||
let (failed, _) = oneshot::channel();
|
||||
let (_, ads) = service.on_request(
|
||||
vec![authority_ids[1].clone()],
|
||||
PeerSet::Validation,
|
||||
failed,
|
||||
ns,
|
||||
ads,
|
||||
).await;
|
||||
let (_, ads) = service
|
||||
.on_request(vec![authority_ids[1].clone()], PeerSet::Validation, failed, ns, ads)
|
||||
.await;
|
||||
|
||||
let state = &service.state[PeerSet::Validation];
|
||||
assert_eq!(state.previously_requested.len(), 1);
|
||||
assert!(state.previously_requested.contains(ads.by_authority_id.get(&authority_ids[1]).unwrap()));
|
||||
assert!(state
|
||||
.previously_requested
|
||||
.contains(ads.by_authority_id.get(&authority_ids[1]).unwrap()));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -279,17 +280,21 @@ mod tests {
|
||||
futures::executor::block_on(async move {
|
||||
let (failed, failed_rx) = oneshot::channel();
|
||||
let unknown = Sr25519Keyring::Ferdie.public().into();
|
||||
let (_, ads) = service.on_request(
|
||||
vec![authority_ids[0].clone(), unknown],
|
||||
PeerSet::Validation,
|
||||
failed,
|
||||
ns,
|
||||
ads,
|
||||
).await;
|
||||
let (_, ads) = service
|
||||
.on_request(
|
||||
vec![authority_ids[0].clone(), unknown],
|
||||
PeerSet::Validation,
|
||||
failed,
|
||||
ns,
|
||||
ads,
|
||||
)
|
||||
.await;
|
||||
|
||||
let state = &service.state[PeerSet::Validation];
|
||||
assert_eq!(state.previously_requested.len(), 1);
|
||||
assert!(state.previously_requested.contains(ads.by_authority_id.get(&authority_ids[0]).unwrap()));
|
||||
assert!(state
|
||||
.previously_requested
|
||||
.contains(ads.by_authority_id.get(&authority_ids[0]).unwrap()));
|
||||
|
||||
let failed = failed_rx.await.unwrap();
|
||||
assert_eq!(failed, 1);
|
||||
|
||||
@@ -14,44 +14,49 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{collections::{HashMap, HashSet, VecDeque}, pin::Pin, time::Duration};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
pin::Pin,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use futures::{FutureExt, StreamExt, channel::oneshot, stream::FuturesUnordered, select, Future};
|
||||
use futures::{channel::oneshot, select, stream::FuturesUnordered, Future, FutureExt, StreamExt};
|
||||
use sp_core::Pair;
|
||||
|
||||
use polkadot_node_network_protocol::{
|
||||
peer_set::PeerSet,
|
||||
request_response::{
|
||||
request::OutgoingResponse,
|
||||
v1::{CollationFetchingRequest, CollationFetchingResponse},
|
||||
IncomingRequest,
|
||||
},
|
||||
v1 as protocol_v1, OurView, PeerId, UnifiedReputationChange as Rep, View,
|
||||
};
|
||||
use polkadot_node_primitives::{PoV, SignedFullStatement, Statement};
|
||||
use polkadot_node_subsystem_util::{
|
||||
metrics::{self, prometheus},
|
||||
runtime::{get_availability_cores, get_group_rotation_info, RuntimeInfo},
|
||||
TimeoutExt,
|
||||
};
|
||||
use polkadot_primitives::v1::{
|
||||
AuthorityDiscoveryId, CandidateHash, CandidateReceipt, CollatorPair, CoreIndex, CoreState, GroupIndex, Hash,
|
||||
Id as ParaId,
|
||||
AuthorityDiscoveryId, CandidateHash, CandidateReceipt, CollatorPair, CoreIndex, CoreState,
|
||||
GroupIndex, Hash, Id as ParaId,
|
||||
};
|
||||
use polkadot_subsystem::{
|
||||
overseer,
|
||||
FromOverseer, OverseerSignal, PerLeafSpan, SubsystemContext, jaeger,
|
||||
messages::{
|
||||
CollatorProtocolMessage, NetworkBridgeEvent, NetworkBridgeMessage,
|
||||
},
|
||||
jaeger,
|
||||
messages::{CollatorProtocolMessage, NetworkBridgeEvent, NetworkBridgeMessage},
|
||||
overseer, FromOverseer, OverseerSignal, PerLeafSpan, SubsystemContext,
|
||||
};
|
||||
use polkadot_node_network_protocol::{
|
||||
OurView, PeerId, UnifiedReputationChange as Rep, View, peer_set::PeerSet,
|
||||
request_response::{
|
||||
IncomingRequest, request::OutgoingResponse, v1::{CollationFetchingRequest, CollationFetchingResponse}
|
||||
},
|
||||
v1 as protocol_v1,
|
||||
};
|
||||
use polkadot_node_subsystem_util::{
|
||||
TimeoutExt,
|
||||
metrics::{self, prometheus},
|
||||
runtime::{RuntimeInfo, get_availability_cores, get_group_rotation_info}
|
||||
};
|
||||
use polkadot_node_primitives::{SignedFullStatement, Statement, PoV};
|
||||
|
||||
use crate::error::{Fatal, NonFatal, log_error};
|
||||
use super::{LOG_TARGET, Result};
|
||||
use super::{Result, LOG_TARGET};
|
||||
use crate::error::{log_error, Fatal, NonFatal};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
const COST_UNEXPECTED_MESSAGE: Rep = Rep::CostMinor("An unexpected message");
|
||||
const COST_APPARENT_FLOOD: Rep = Rep::CostMinor("Message received when previous one was still being processed");
|
||||
const COST_APPARENT_FLOOD: Rep =
|
||||
Rep::CostMinor("Message received when previous one was still being processed");
|
||||
|
||||
/// Time after starting an upload to a validator we will start another one to the next validator,
|
||||
/// even if the upload was not finished yet.
|
||||
@@ -102,9 +107,9 @@ struct MetricsInner {
|
||||
}
|
||||
|
||||
impl metrics::Metrics for Metrics {
|
||||
fn try_register(registry: &prometheus::Registry)
|
||||
-> std::result::Result<Self, prometheus::PrometheusError>
|
||||
{
|
||||
fn try_register(
|
||||
registry: &prometheus::Registry,
|
||||
) -> std::result::Result<Self, prometheus::PrometheusError> {
|
||||
let metrics = MetricsInner {
|
||||
advertisements_made: prometheus::register(
|
||||
prometheus::Counter::new(
|
||||
@@ -128,12 +133,10 @@ impl metrics::Metrics for Metrics {
|
||||
registry,
|
||||
)?,
|
||||
process_msg: prometheus::register(
|
||||
prometheus::Histogram::with_opts(
|
||||
prometheus::HistogramOpts::new(
|
||||
"parachain_collator_protocol_collator_process_msg",
|
||||
"Time spent within `collator_protocol_collator::process_msg`",
|
||||
)
|
||||
)?,
|
||||
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
|
||||
"parachain_collator_protocol_collator_process_msg",
|
||||
"Time spent within `collator_protocol_collator::process_msg`",
|
||||
))?,
|
||||
registry,
|
||||
)?,
|
||||
};
|
||||
@@ -157,8 +160,11 @@ struct ValidatorGroup {
|
||||
|
||||
impl ValidatorGroup {
|
||||
/// Returns `true` if we should advertise our collation to the given peer.
|
||||
fn should_advertise_to(&self, peer_ids: &HashMap<PeerId, AuthorityDiscoveryId>, peer: &PeerId)
|
||||
-> bool {
|
||||
fn should_advertise_to(
|
||||
&self,
|
||||
peer_ids: &HashMap<PeerId, AuthorityDiscoveryId>,
|
||||
peer: &PeerId,
|
||||
) -> bool {
|
||||
match peer_ids.get(peer) {
|
||||
Some(discovery_id) => !self.advertised_to.contains(discovery_id),
|
||||
None => false,
|
||||
@@ -166,7 +172,11 @@ impl ValidatorGroup {
|
||||
}
|
||||
|
||||
/// Should be called after we advertised our collation to the given `peer` to keep track of it.
|
||||
fn advertised_to_peer(&mut self, peer_ids: &HashMap<PeerId, AuthorityDiscoveryId>, peer: &PeerId) {
|
||||
fn advertised_to_peer(
|
||||
&mut self,
|
||||
peer_ids: &HashMap<PeerId, AuthorityDiscoveryId>,
|
||||
peer: &PeerId,
|
||||
) {
|
||||
if let Some(validator_id) = peer_ids.get(peer) {
|
||||
self.advertised_to.insert(validator_id.clone());
|
||||
}
|
||||
@@ -175,10 +185,7 @@ impl ValidatorGroup {
|
||||
|
||||
impl From<HashSet<AuthorityDiscoveryId>> for ValidatorGroup {
|
||||
fn from(discovery_ids: HashSet<AuthorityDiscoveryId>) -> Self {
|
||||
Self {
|
||||
discovery_ids,
|
||||
advertised_to: HashSet::new(),
|
||||
}
|
||||
Self { discovery_ids, advertised_to: HashSet::new() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,7 +236,8 @@ struct WaitingCollationFetches {
|
||||
waiting_peers: HashSet<PeerId>,
|
||||
}
|
||||
|
||||
type ActiveCollationFetches = FuturesUnordered<Pin<Box<dyn Future<Output = (Hash, PeerId)> + Send + 'static>>>;
|
||||
type ActiveCollationFetches =
|
||||
FuturesUnordered<Pin<Box<dyn Future<Output = (Hash, PeerId)> + Send + 'static>>>;
|
||||
|
||||
struct State {
|
||||
/// Our network peer id.
|
||||
@@ -344,12 +352,12 @@ where
|
||||
"distribute collation message parent is outside of our view",
|
||||
);
|
||||
|
||||
return Ok(());
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
// We have already seen collation for this relay parent.
|
||||
if state.collations.contains_key(&relay_parent) {
|
||||
return Ok(());
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
// Determine which core the para collated-on is assigned to.
|
||||
@@ -365,12 +373,12 @@ where
|
||||
);
|
||||
|
||||
return Ok(())
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Determine the group on that core and the next group on that core.
|
||||
let (current_validators, next_validators) =
|
||||
determine_our_validators(ctx, runtime, our_core, num_cores, relay_parent,).await?;
|
||||
determine_our_validators(ctx, runtime, our_core, num_cores, relay_parent).await?;
|
||||
|
||||
if current_validators.validators.is_empty() && next_validators.validators.is_empty() {
|
||||
tracing::warn!(
|
||||
@@ -379,7 +387,7 @@ where
|
||||
"there are no validators assigned to core",
|
||||
);
|
||||
|
||||
return Ok(());
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
tracing::debug!(
|
||||
@@ -394,16 +402,19 @@ where
|
||||
"Accepted collation, connecting to validators."
|
||||
);
|
||||
|
||||
let validator_group: HashSet<_> = current_validators.validators.iter().map(Clone::clone).collect();
|
||||
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(
|
||||
ctx,
|
||||
current_validators.validators
|
||||
current_validators
|
||||
.validators
|
||||
.into_iter()
|
||||
.chain(next_validators.validators.into_iter())
|
||||
.collect(),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
state.our_validators_groups.insert(relay_parent, validator_group.into());
|
||||
|
||||
@@ -411,7 +422,9 @@ where
|
||||
state.collation_result_senders.insert(receipt.hash(), result_sender);
|
||||
}
|
||||
|
||||
state.collations.insert(relay_parent, Collation { receipt, pov, status: CollationStatus::Created });
|
||||
state
|
||||
.collations
|
||||
.insert(relay_parent, Collation { receipt, pov, status: CollationStatus::Created });
|
||||
|
||||
let interested = state.peers_interested_in_leaf(&relay_parent);
|
||||
// Make sure already connected peers get collations:
|
||||
@@ -438,7 +451,7 @@ where
|
||||
for (idx, core) in cores.iter().enumerate() {
|
||||
if let CoreState::Scheduled(occupied) = core {
|
||||
if occupied.para_id == para_id {
|
||||
return Ok(Some(((idx as u32).into(), cores.len())));
|
||||
return Ok(Some(((idx as u32).into(), cores.len())))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -470,7 +483,8 @@ where
|
||||
Context: overseer::SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
{
|
||||
let session_index = runtime.get_session_index(ctx.sender(), relay_parent).await?;
|
||||
let info = &runtime.get_session_info_by_index(ctx.sender(), relay_parent, session_index)
|
||||
let info = &runtime
|
||||
.get_session_info_by_index(ctx.sender(), relay_parent, session_index)
|
||||
.await?
|
||||
.session_info;
|
||||
tracing::debug!(target: LOG_TARGET, ?session_index, "Received session info");
|
||||
@@ -478,34 +492,31 @@ where
|
||||
let rotation_info = get_group_rotation_info(ctx, relay_parent).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_validators = groups
|
||||
.get(current_group_index.0 as usize)
|
||||
.map(|v| v.as_slice())
|
||||
.unwrap_or_default();
|
||||
|
||||
let next_group_idx = (current_group_index.0 as usize + 1) % groups.len();
|
||||
let next_validators = groups.get(next_group_idx).map(|v| v.as_slice()).unwrap_or_default();
|
||||
|
||||
let validators = &info.discovery_keys;
|
||||
|
||||
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 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 current_validators = GroupValidators {
|
||||
group: current_group_index,
|
||||
validators: current_validators,
|
||||
};
|
||||
let next_validators = GroupValidators {
|
||||
group: GroupIndex(next_group_idx as u32),
|
||||
validators: next_validators,
|
||||
};
|
||||
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))
|
||||
}
|
||||
|
||||
/// Issue a `Declare` collation message to the given `peer`.
|
||||
async fn declare<Context>(
|
||||
ctx: &mut Context,
|
||||
state: &mut State,
|
||||
peer: PeerId,
|
||||
)
|
||||
async fn declare<Context>(ctx: &mut Context, state: &mut State, peer: PeerId)
|
||||
where
|
||||
Context: SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
Context: overseer::SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
@@ -519,21 +530,17 @@ where
|
||||
state.collator_pair.sign(&declare_signature_payload),
|
||||
);
|
||||
|
||||
ctx.send_message(
|
||||
NetworkBridgeMessage::SendCollationMessage(
|
||||
vec![peer],
|
||||
protocol_v1::CollationProtocol::CollatorProtocol(wire_message),
|
||||
)
|
||||
).await;
|
||||
ctx.send_message(NetworkBridgeMessage::SendCollationMessage(
|
||||
vec![peer],
|
||||
protocol_v1::CollationProtocol::CollatorProtocol(wire_message),
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Issue a connection request to a set of validators and
|
||||
/// revoke the previous connection request.
|
||||
async fn connect_to_validators<Context>(
|
||||
ctx: &mut Context,
|
||||
validator_ids: Vec<AuthorityDiscoveryId>,
|
||||
)
|
||||
async fn connect_to_validators<Context>(ctx: &mut Context, validator_ids: Vec<AuthorityDiscoveryId>)
|
||||
where
|
||||
Context: SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
Context: overseer::SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
@@ -542,8 +549,11 @@ where
|
||||
// will reissue a new request on new collation
|
||||
let (failed, _) = oneshot::channel();
|
||||
ctx.send_message(NetworkBridgeMessage::ConnectToValidators {
|
||||
validator_ids, peer_set: PeerSet::Collation, failed,
|
||||
}).await;
|
||||
validator_ids,
|
||||
peer_set: PeerSet::Collation,
|
||||
failed,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Advertise collation to the given `peer`.
|
||||
@@ -555,12 +565,12 @@ async fn advertise_collation<Context>(
|
||||
state: &mut State,
|
||||
relay_parent: Hash,
|
||||
peer: PeerId,
|
||||
)
|
||||
where
|
||||
) where
|
||||
Context: SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
Context: overseer::SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
{
|
||||
let should_advertise = state.our_validators_groups
|
||||
let should_advertise = state
|
||||
.our_validators_groups
|
||||
.get(&relay_parent)
|
||||
.map(|g| g.should_advertise_to(&state.peer_ids, &peer))
|
||||
.unwrap_or(false);
|
||||
@@ -583,7 +593,7 @@ where
|
||||
"Not advertising collation as we already advertised it to this validator.",
|
||||
);
|
||||
return
|
||||
}
|
||||
},
|
||||
(Some(collation), true) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
@@ -595,16 +605,13 @@ where
|
||||
},
|
||||
}
|
||||
|
||||
let wire_message = protocol_v1::CollatorProtocolMessage::AdvertiseCollation(
|
||||
relay_parent,
|
||||
);
|
||||
let wire_message = protocol_v1::CollatorProtocolMessage::AdvertiseCollation(relay_parent);
|
||||
|
||||
ctx.send_message(
|
||||
NetworkBridgeMessage::SendCollationMessage(
|
||||
vec![peer.clone()],
|
||||
protocol_v1::CollationProtocol::CollatorProtocol(wire_message),
|
||||
)
|
||||
).await;
|
||||
ctx.send_message(NetworkBridgeMessage::SendCollationMessage(
|
||||
vec![peer.clone()],
|
||||
protocol_v1::CollationProtocol::CollatorProtocol(wire_message),
|
||||
))
|
||||
.await;
|
||||
|
||||
if let Some(validators) = state.our_validators_groups.get_mut(&relay_parent) {
|
||||
validators.advertised_to_peer(&state.peer_ids, &peer);
|
||||
@@ -631,10 +638,12 @@ where
|
||||
match msg {
|
||||
CollateOn(id) => {
|
||||
state.collating_on = Some(id);
|
||||
}
|
||||
},
|
||||
DistributeCollation(receipt, pov, result_sender) => {
|
||||
let _span1 = state.span_per_relay_parent
|
||||
.get(&receipt.descriptor.relay_parent).map(|s| s.child("distributing-collation"));
|
||||
let _span1 = state
|
||||
.span_per_relay_parent
|
||||
.get(&receipt.descriptor.relay_parent)
|
||||
.map(|s| s.child("distributing-collation"));
|
||||
let _span2 = jaeger::Span::new(&pov, "distributing-collation");
|
||||
match state.collating_on {
|
||||
Some(id) if receipt.descriptor.para_id != id => {
|
||||
@@ -646,32 +655,28 @@ where
|
||||
collating_on = %id,
|
||||
"DistributeCollation for unexpected para_id",
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(id) => {
|
||||
distribute_collation(ctx, runtime, state, id, receipt, pov, result_sender).await?;
|
||||
}
|
||||
distribute_collation(ctx, runtime, state, id, receipt, pov, result_sender)
|
||||
.await?;
|
||||
},
|
||||
None => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
para_id = %receipt.descriptor.para_id,
|
||||
"DistributeCollation message while not collating on any",
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
ReportCollator(_) => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
"ReportCollator message is not expected on the collator side of the protocol",
|
||||
);
|
||||
}
|
||||
},
|
||||
NetworkBridgeUpdateV1(event) => {
|
||||
if let Err(e) = handle_network_msg(
|
||||
ctx,
|
||||
runtime,
|
||||
state,
|
||||
event,
|
||||
).await {
|
||||
if let Err(e) = handle_network_msg(ctx, runtime, state, event).await {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
err = ?e,
|
||||
@@ -680,11 +685,16 @@ where
|
||||
}
|
||||
},
|
||||
CollationFetchingRequest(incoming) => {
|
||||
let _span = state.span_per_relay_parent.get(&incoming.payload.relay_parent).map(|s| s.child("request-collation"));
|
||||
let _span = state
|
||||
.span_per_relay_parent
|
||||
.get(&incoming.payload.relay_parent)
|
||||
.map(|s| s.child("request-collation"));
|
||||
match state.collating_on {
|
||||
Some(our_para_id) => {
|
||||
Some(our_para_id) =>
|
||||
if our_para_id == incoming.payload.para_id {
|
||||
let (receipt, pov) = if let Some(collation) = state.collations.get_mut(&incoming.payload.relay_parent) {
|
||||
let (receipt, pov) = if let Some(collation) =
|
||||
state.collations.get_mut(&incoming.payload.relay_parent)
|
||||
{
|
||||
collation.status.advance_to_requested();
|
||||
(collation.receipt.clone(), collation.pov.clone())
|
||||
} else {
|
||||
@@ -694,23 +704,28 @@ where
|
||||
"received a `RequestCollation` for a relay parent we don't have collation stored.",
|
||||
);
|
||||
|
||||
return Ok(());
|
||||
return Ok(())
|
||||
};
|
||||
|
||||
state.metrics.on_collation_sent_requested();
|
||||
|
||||
let _span = _span.as_ref().map(|s| s.child("sending"));
|
||||
|
||||
let waiting = state.waiting_collation_fetches.entry(incoming.payload.relay_parent).or_default();
|
||||
let waiting = state
|
||||
.waiting_collation_fetches
|
||||
.entry(incoming.payload.relay_parent)
|
||||
.or_default();
|
||||
|
||||
if !waiting.waiting_peers.insert(incoming.peer) {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Dropping incoming request as peer has a request in flight already."
|
||||
);
|
||||
ctx.send_message(
|
||||
NetworkBridgeMessage::ReportPeer(incoming.peer, COST_APPARENT_FLOOD)
|
||||
).await;
|
||||
ctx.send_message(NetworkBridgeMessage::ReportPeer(
|
||||
incoming.peer,
|
||||
COST_APPARENT_FLOOD,
|
||||
))
|
||||
.await;
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
@@ -727,17 +742,16 @@ where
|
||||
our_para_id = %our_para_id,
|
||||
"received a `CollationFetchingRequest` for unexpected para_id",
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
for_para_id = %incoming.payload.para_id,
|
||||
"received a `RequestCollation` while not collating on any para",
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
@@ -763,24 +777,24 @@ async fn send_collation(
|
||||
};
|
||||
|
||||
if let Err(_) = request.send_outgoing_response(response) {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
"Sending collation response failed",
|
||||
);
|
||||
tracing::warn!(target: LOG_TARGET, "Sending collation response failed",);
|
||||
}
|
||||
|
||||
state.active_collation_fetches.push(async move {
|
||||
let r = rx.timeout(MAX_UNSHARED_UPLOAD_TIME).await;
|
||||
if r.is_none() {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
?relay_parent,
|
||||
?peer_id,
|
||||
"Sending collation to validator timed out, carrying on with next validator."
|
||||
);
|
||||
state.active_collation_fetches.push(
|
||||
async move {
|
||||
let r = rx.timeout(MAX_UNSHARED_UPLOAD_TIME).await;
|
||||
if r.is_none() {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
?relay_parent,
|
||||
?peer_id,
|
||||
"Sending collation to validator timed out, carrying on with next validator."
|
||||
);
|
||||
}
|
||||
(relay_parent, peer_id)
|
||||
}
|
||||
(relay_parent, peer_id)
|
||||
}.boxed());
|
||||
.boxed(),
|
||||
);
|
||||
|
||||
state.metrics.on_collation_sent();
|
||||
}
|
||||
@@ -808,10 +822,9 @@ where
|
||||
);
|
||||
|
||||
// If we are declared to, this is another collator, and we should disconnect.
|
||||
ctx.send_message(
|
||||
NetworkBridgeMessage::DisconnectPeer(origin, PeerSet::Collation)
|
||||
).await;
|
||||
}
|
||||
ctx.send_message(NetworkBridgeMessage::DisconnectPeer(origin, PeerSet::Collation))
|
||||
.await;
|
||||
},
|
||||
AdvertiseCollation(_) => {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
@@ -819,15 +832,16 @@ where
|
||||
"AdvertiseCollation message is not expected on the collator side of the protocol",
|
||||
);
|
||||
|
||||
ctx.send_message(
|
||||
NetworkBridgeMessage::ReportPeer(origin.clone(), COST_UNEXPECTED_MESSAGE)
|
||||
).await;
|
||||
ctx.send_message(NetworkBridgeMessage::ReportPeer(
|
||||
origin.clone(),
|
||||
COST_UNEXPECTED_MESSAGE,
|
||||
))
|
||||
.await;
|
||||
|
||||
// If we are advertised to, this is another collator, and we should disconnect.
|
||||
ctx.send_message(
|
||||
NetworkBridgeMessage::DisconnectPeer(origin, PeerSet::Collation)
|
||||
).await;
|
||||
}
|
||||
ctx.send_message(NetworkBridgeMessage::DisconnectPeer(origin, PeerSet::Collation))
|
||||
.await;
|
||||
},
|
||||
CollationSeconded(relay_parent, statement) => {
|
||||
if !matches!(statement.unchecked_payload(), Statement::Seconded(_)) {
|
||||
tracing::warn!(
|
||||
@@ -837,12 +851,13 @@ where
|
||||
"Collation seconded message received with none-seconded statement.",
|
||||
);
|
||||
} else {
|
||||
let statement = runtime.check_signature(ctx.sender(), relay_parent, statement)
|
||||
let statement = runtime
|
||||
.check_signature(ctx.sender(), relay_parent, statement)
|
||||
.await?
|
||||
.map_err(NonFatal::InvalidStatementSignature)?;
|
||||
|
||||
let removed = state.collation_result_senders
|
||||
.remove(&statement.payload().candidate_hash());
|
||||
let removed =
|
||||
state.collation_result_senders.remove(&statement.payload().candidate_hash());
|
||||
|
||||
if let Some(sender) = removed {
|
||||
tracing::trace!(
|
||||
@@ -854,7 +869,7 @@ where
|
||||
let _ = sender.send(statement);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -866,8 +881,7 @@ async fn handle_peer_view_change<Context>(
|
||||
state: &mut State,
|
||||
peer_id: PeerId,
|
||||
view: View,
|
||||
)
|
||||
where
|
||||
) where
|
||||
Context: SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
Context: overseer::SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
{
|
||||
@@ -899,12 +913,7 @@ where
|
||||
PeerConnected(peer_id, observed_role, maybe_authority) => {
|
||||
// If it is possible that a disconnected validator would attempt a reconnect
|
||||
// it should be handled here.
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?peer_id,
|
||||
?observed_role,
|
||||
"Peer connected",
|
||||
);
|
||||
tracing::trace!(target: LOG_TARGET, ?peer_id, ?observed_role, "Peer connected",);
|
||||
if let Some(authority) = maybe_authority {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
@@ -916,49 +925,33 @@ where
|
||||
|
||||
declare(ctx, state, peer_id).await;
|
||||
}
|
||||
}
|
||||
},
|
||||
PeerViewChange(peer_id, view) => {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?peer_id,
|
||||
?view,
|
||||
"Peer view change",
|
||||
);
|
||||
tracing::trace!(target: LOG_TARGET, ?peer_id, ?view, "Peer view change",);
|
||||
handle_peer_view_change(ctx, state, peer_id, view).await;
|
||||
}
|
||||
},
|
||||
PeerDisconnected(peer_id) => {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?peer_id,
|
||||
"Peer disconnected",
|
||||
);
|
||||
tracing::trace!(target: LOG_TARGET, ?peer_id, "Peer disconnected",);
|
||||
state.peer_views.remove(&peer_id);
|
||||
state.peer_ids.remove(&peer_id);
|
||||
}
|
||||
},
|
||||
OurViewChange(view) => {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?view,
|
||||
"Own view change",
|
||||
);
|
||||
tracing::trace!(target: LOG_TARGET, ?view, "Own view change",);
|
||||
handle_our_view_change(state, view).await?;
|
||||
}
|
||||
},
|
||||
PeerMessage(remote, msg) => {
|
||||
handle_incoming_peer_message(ctx, runtime, state, remote, msg).await?;
|
||||
}
|
||||
},
|
||||
NewGossipTopology(..) => {
|
||||
// impossibru!
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handles our view changes.
|
||||
async fn handle_our_view_change(
|
||||
state: &mut State,
|
||||
view: OurView,
|
||||
) -> Result<()> {
|
||||
async fn handle_our_view_change(state: &mut State, view: OurView) -> Result<()> {
|
||||
for removed in state.view.difference(&view) {
|
||||
tracing::debug!(target: LOG_TARGET, relay_parent = ?removed, "Removing relay parent because our view changed.");
|
||||
|
||||
@@ -983,7 +976,7 @@ async fn handle_our_view_change(
|
||||
candidate_hash = ?collation.receipt.hash(),
|
||||
pov_hash = ?collation.pov.hash(),
|
||||
"Collation was requested.",
|
||||
)
|
||||
),
|
||||
}
|
||||
}
|
||||
state.our_validators_groups.remove(removed);
|
||||
@@ -1005,7 +998,7 @@ pub(crate) async fn run<Context>(
|
||||
) -> Result<()>
|
||||
where
|
||||
Context: SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
Context: overseer::SubsystemContext<Message = CollatorProtocolMessage>
|
||||
Context: overseer::SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
{
|
||||
use OverseerSignal::*;
|
||||
|
||||
|
||||
@@ -26,21 +26,17 @@ use sp_core::{crypto::Pair, Decode};
|
||||
use sp_keyring::Sr25519Keyring;
|
||||
use sp_runtime::traits::AppVerify;
|
||||
|
||||
use polkadot_node_network_protocol::{
|
||||
our_view,
|
||||
view,
|
||||
request_response::request::IncomingRequest,
|
||||
};
|
||||
use polkadot_node_network_protocol::{our_view, request_response::request::IncomingRequest, view};
|
||||
use polkadot_node_primitives::BlockData;
|
||||
use polkadot_node_subsystem_util::TimeoutExt;
|
||||
use polkadot_primitives::v1::{
|
||||
AuthorityDiscoveryId, CandidateDescriptor, CollatorPair, GroupRotationInfo, ScheduledCore, SessionIndex,
|
||||
SessionInfo, ValidatorId, ValidatorIndex,
|
||||
AuthorityDiscoveryId, CandidateDescriptor, CollatorPair, GroupRotationInfo, ScheduledCore,
|
||||
SessionIndex, SessionInfo, ValidatorId, ValidatorIndex,
|
||||
};
|
||||
use polkadot_node_primitives::BlockData;
|
||||
use polkadot_subsystem::{
|
||||
jaeger,
|
||||
messages::{AllMessages, RuntimeApiMessage, RuntimeApiRequest},
|
||||
ActiveLeavesUpdate, ActivatedLeaf, LeafStatus,
|
||||
ActivatedLeaf, ActiveLeavesUpdate, LeafStatus,
|
||||
};
|
||||
use polkadot_subsystem_testhelpers as test_helpers;
|
||||
|
||||
@@ -103,22 +99,17 @@ impl Default for TestState {
|
||||
let validator_public = validator_pubkeys(&validators);
|
||||
let discovery_keys = validator_authority_id(&validators);
|
||||
|
||||
let validator_peer_id = std::iter::repeat_with(|| PeerId::random())
|
||||
.take(discovery_keys.len())
|
||||
.collect();
|
||||
let validator_peer_id =
|
||||
std::iter::repeat_with(|| PeerId::random()).take(discovery_keys.len()).collect();
|
||||
|
||||
let validator_groups = vec![vec![2, 0, 4], vec![3, 2, 4]]
|
||||
.into_iter().map(|g| g.into_iter().map(ValidatorIndex).collect()).collect();
|
||||
let group_rotation_info = GroupRotationInfo {
|
||||
session_start_block: 0,
|
||||
group_rotation_frequency: 100,
|
||||
now: 1,
|
||||
};
|
||||
.into_iter()
|
||||
.map(|g| g.into_iter().map(ValidatorIndex).collect())
|
||||
.collect();
|
||||
let group_rotation_info =
|
||||
GroupRotationInfo { session_start_block: 0, group_rotation_frequency: 100, now: 1 };
|
||||
|
||||
let availability_core = CoreState::Scheduled(ScheduledCore {
|
||||
para_id,
|
||||
collator: None,
|
||||
});
|
||||
let availability_core = CoreState::Scheduled(ScheduledCore { para_id, collator: None });
|
||||
|
||||
let relay_parent = Hash::random();
|
||||
|
||||
@@ -155,7 +146,10 @@ impl TestState {
|
||||
}
|
||||
|
||||
fn current_group_validator_peer_ids(&self) -> Vec<PeerId> {
|
||||
self.current_group_validator_indices().iter().map(|i| self.validator_peer_id[i.0 as usize].clone()).collect()
|
||||
self.current_group_validator_indices()
|
||||
.iter()
|
||||
.map(|i| self.validator_peer_id[i.0 as usize].clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn current_group_validator_authority_ids(&self) -> Vec<AuthorityDiscoveryId> {
|
||||
@@ -169,7 +163,11 @@ impl TestState {
|
||||
///
|
||||
/// If `merge_views == true` it means the subsystem will be informed that we are working on the old `relay_parent`
|
||||
/// and the new one.
|
||||
async fn advance_to_new_round(&mut self, virtual_overseer: &mut VirtualOverseer, merge_views: bool) {
|
||||
async fn advance_to_new_round(
|
||||
&mut self,
|
||||
virtual_overseer: &mut VirtualOverseer,
|
||||
merge_views: bool,
|
||||
) {
|
||||
let old_relay_parent = self.relay_parent;
|
||||
|
||||
while self.relay_parent == old_relay_parent {
|
||||
@@ -184,8 +182,11 @@ impl TestState {
|
||||
|
||||
overseer_send(
|
||||
virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::OurViewChange(our_view)),
|
||||
).await;
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::OurViewChange(
|
||||
our_view,
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,14 +203,8 @@ fn test_harness<T: Future<Output = VirtualOverseer>>(
|
||||
) {
|
||||
let _ = env_logger::builder()
|
||||
.is_test(true)
|
||||
.filter(
|
||||
Some("polkadot_collator_protocol"),
|
||||
log::LevelFilter::Trace,
|
||||
)
|
||||
.filter(
|
||||
Some(LOG_TARGET),
|
||||
log::LevelFilter::Trace,
|
||||
)
|
||||
.filter(Some("polkadot_collator_protocol"), log::LevelFilter::Trace)
|
||||
.filter(Some(LOG_TARGET), log::LevelFilter::Trace)
|
||||
.try_init();
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
@@ -223,18 +218,20 @@ fn test_harness<T: Future<Output = VirtualOverseer>>(
|
||||
futures::pin_mut!(test_fut);
|
||||
futures::pin_mut!(subsystem);
|
||||
|
||||
executor::block_on(future::join(async move {
|
||||
let mut overseer = test_fut.await;
|
||||
overseer_signal(&mut overseer, OverseerSignal::Conclude).await;
|
||||
}, subsystem)).1.unwrap();
|
||||
executor::block_on(future::join(
|
||||
async move {
|
||||
let mut overseer = test_fut.await;
|
||||
overseer_signal(&mut overseer, OverseerSignal::Conclude).await;
|
||||
},
|
||||
subsystem,
|
||||
))
|
||||
.1
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
const TIMEOUT: Duration = Duration::from_millis(100);
|
||||
|
||||
async fn overseer_send(
|
||||
overseer: &mut VirtualOverseer,
|
||||
msg: CollatorProtocolMessage,
|
||||
) {
|
||||
async fn overseer_send(overseer: &mut VirtualOverseer, msg: CollatorProtocolMessage) {
|
||||
tracing::trace!(?msg, "sending message");
|
||||
overseer
|
||||
.send(FromOverseer::Communication { msg })
|
||||
@@ -243,9 +240,7 @@ async fn overseer_send(
|
||||
.expect(&format!("{:?} is more than enough for sending messages.", TIMEOUT));
|
||||
}
|
||||
|
||||
async fn overseer_recv(
|
||||
overseer: &mut VirtualOverseer,
|
||||
) -> AllMessages {
|
||||
async fn overseer_recv(overseer: &mut VirtualOverseer) -> AllMessages {
|
||||
let msg = overseer_recv_with_timeout(overseer, TIMEOUT)
|
||||
.await
|
||||
.expect(&format!("{:?} is more than enough to receive messages", TIMEOUT));
|
||||
@@ -260,16 +255,10 @@ async fn overseer_recv_with_timeout(
|
||||
timeout: Duration,
|
||||
) -> Option<AllMessages> {
|
||||
tracing::trace!("waiting for message...");
|
||||
overseer
|
||||
.recv()
|
||||
.timeout(timeout)
|
||||
.await
|
||||
overseer.recv().timeout(timeout).await
|
||||
}
|
||||
|
||||
async fn overseer_signal(
|
||||
overseer: &mut VirtualOverseer,
|
||||
signal: OverseerSignal,
|
||||
) {
|
||||
async fn overseer_signal(overseer: &mut VirtualOverseer, signal: OverseerSignal) {
|
||||
overseer
|
||||
.send(FromOverseer::Signal(signal))
|
||||
.timeout(TIMEOUT)
|
||||
@@ -279,10 +268,7 @@ async fn overseer_signal(
|
||||
|
||||
// Setup the system by sending the `CollateOn`, `ActiveLeaves` and `OurViewChange` messages.
|
||||
async fn setup_system(virtual_overseer: &mut VirtualOverseer, test_state: &TestState) {
|
||||
overseer_send(
|
||||
virtual_overseer,
|
||||
CollatorProtocolMessage::CollateOn(test_state.para_id),
|
||||
).await;
|
||||
overseer_send(virtual_overseer, CollatorProtocolMessage::CollateOn(test_state.para_id)).await;
|
||||
|
||||
overseer_signal(
|
||||
virtual_overseer,
|
||||
@@ -292,14 +278,16 @@ async fn setup_system(virtual_overseer: &mut VirtualOverseer, test_state: &TestS
|
||||
status: LeafStatus::Fresh,
|
||||
span: Arc::new(jaeger::Span::Disabled),
|
||||
})),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
overseer_send(
|
||||
virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::OurViewChange(our_view![test_state.relay_parent]),
|
||||
),
|
||||
).await;
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::OurViewChange(
|
||||
our_view![test_state.relay_parent],
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Result of [`distribute_collation`]
|
||||
@@ -316,9 +304,7 @@ async fn distribute_collation(
|
||||
should_connect: bool,
|
||||
) -> DistributeCollation {
|
||||
// Now we want to distribute a PoVBlock
|
||||
let pov_block = PoV {
|
||||
block_data: BlockData(vec![42, 43, 44]),
|
||||
};
|
||||
let pov_block = PoV { block_data: BlockData(vec![42, 43, 44]) };
|
||||
|
||||
let pov_hash = pov_block.hash();
|
||||
|
||||
@@ -327,12 +313,14 @@ async fn distribute_collation(
|
||||
relay_parent: test_state.relay_parent,
|
||||
pov_hash,
|
||||
..Default::default()
|
||||
}.build();
|
||||
}
|
||||
.build();
|
||||
|
||||
overseer_send(
|
||||
virtual_overseer,
|
||||
CollatorProtocolMessage::DistributeCollation(candidate.clone(), pov_block.clone(), None),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
// obtain the availability cores.
|
||||
assert_matches!(
|
||||
@@ -355,7 +343,7 @@ async fn distribute_collation(
|
||||
)) => {
|
||||
assert_eq!(relay_parent, test_state.relay_parent);
|
||||
tx.send(Ok(test_state.current_session_index())).unwrap();
|
||||
}
|
||||
},
|
||||
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
@@ -365,22 +353,22 @@ async fn distribute_collation(
|
||||
assert_eq!(index, test_state.current_session_index());
|
||||
|
||||
tx.send(Ok(Some(test_state.session_info.clone()))).unwrap();
|
||||
}
|
||||
},
|
||||
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
RuntimeApiRequest::ValidatorGroups(tx)
|
||||
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();
|
||||
)))
|
||||
.unwrap();
|
||||
// This call is mandatory - we are done:
|
||||
break;
|
||||
}
|
||||
other =>
|
||||
panic!("Unexpected message received: {:?}", other),
|
||||
break
|
||||
},
|
||||
other => panic!("Unexpected message received: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,35 +383,33 @@ async fn distribute_collation(
|
||||
);
|
||||
}
|
||||
|
||||
DistributeCollation {
|
||||
candidate,
|
||||
pov_block,
|
||||
}
|
||||
DistributeCollation { candidate, pov_block }
|
||||
}
|
||||
|
||||
/// Connect a peer
|
||||
async fn connect_peer(
|
||||
virtual_overseer: &mut VirtualOverseer,
|
||||
peer: PeerId,
|
||||
authority_id: Option<AuthorityDiscoveryId>
|
||||
authority_id: Option<AuthorityDiscoveryId>,
|
||||
) {
|
||||
overseer_send(
|
||||
virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::PeerConnected(
|
||||
peer.clone(),
|
||||
polkadot_node_network_protocol::ObservedRole::Authority,
|
||||
authority_id,
|
||||
),
|
||||
),
|
||||
).await;
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerConnected(
|
||||
peer.clone(),
|
||||
polkadot_node_network_protocol::ObservedRole::Authority,
|
||||
authority_id,
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
overseer_send(
|
||||
virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::PeerViewChange(peer, view![]),
|
||||
),
|
||||
).await;
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerViewChange(
|
||||
peer,
|
||||
view![],
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Disconnect a peer
|
||||
@@ -431,7 +417,8 @@ async fn disconnect_peer(virtual_overseer: &mut VirtualOverseer, peer: PeerId) {
|
||||
overseer_send(
|
||||
virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerDisconnected(peer)),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Check that the next received message is a `Declare` message.
|
||||
@@ -496,13 +483,19 @@ async fn expect_advertise_collation_msg(
|
||||
}
|
||||
|
||||
/// Send a message that the given peer's view changed.
|
||||
async fn send_peer_view_change(virtual_overseer: &mut VirtualOverseer, peer: &PeerId, hashes: Vec<Hash>) {
|
||||
async fn send_peer_view_change(
|
||||
virtual_overseer: &mut VirtualOverseer,
|
||||
peer: &PeerId,
|
||||
hashes: Vec<Hash>,
|
||||
) {
|
||||
overseer_send(
|
||||
virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::PeerViewChange(peer.clone(), View::new(hashes, 0)),
|
||||
),
|
||||
).await;
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerViewChange(
|
||||
peer.clone(),
|
||||
View::new(hashes, 0),
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -519,7 +512,8 @@ fn advertise_and_send_collation() {
|
||||
let DistributeCollation { candidate, pov_block } =
|
||||
distribute_collation(&mut virtual_overseer, &test_state, true).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()
|
||||
.zip(test_state.current_group_validator_peer_ids())
|
||||
{
|
||||
@@ -546,33 +540,31 @@ fn advertise_and_send_collation() {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::CollationFetchingRequest(
|
||||
IncomingRequest::new(
|
||||
CollatorProtocolMessage::CollationFetchingRequest(IncomingRequest::new(
|
||||
peer,
|
||||
CollationFetchingRequest {
|
||||
relay_parent: test_state.relay_parent,
|
||||
para_id: test_state.para_id,
|
||||
},
|
||||
tx,
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
// Second request by same validator should get dropped and peer reported:
|
||||
{
|
||||
let (tx, rx) = oneshot::channel();
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::CollationFetchingRequest(IncomingRequest::new(
|
||||
peer,
|
||||
CollationFetchingRequest {
|
||||
relay_parent: test_state.relay_parent,
|
||||
para_id: test_state.para_id,
|
||||
},
|
||||
tx,
|
||||
)
|
||||
)),
|
||||
)
|
||||
).await;
|
||||
// Second request by same validator should get dropped and peer reported:
|
||||
{
|
||||
let (tx, rx) = oneshot::channel();
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::CollationFetchingRequest(
|
||||
IncomingRequest::new(
|
||||
peer,
|
||||
CollationFetchingRequest {
|
||||
relay_parent: test_state.relay_parent,
|
||||
para_id: test_state.para_id,
|
||||
},
|
||||
tx,
|
||||
)
|
||||
)
|
||||
).await;
|
||||
.await;
|
||||
assert_matches!(
|
||||
overseer_recv(&mut virtual_overseer).await,
|
||||
AllMessages::NetworkBridge(NetworkBridgeMessage::ReportPeer(bad_peer, _)) => {
|
||||
@@ -609,17 +601,16 @@ fn advertise_and_send_collation() {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::CollationFetchingRequest(
|
||||
IncomingRequest::new(
|
||||
peer,
|
||||
CollationFetchingRequest {
|
||||
relay_parent: old_relay_parent,
|
||||
para_id: test_state.para_id,
|
||||
},
|
||||
tx,
|
||||
)
|
||||
)
|
||||
).await;
|
||||
CollatorProtocolMessage::CollationFetchingRequest(IncomingRequest::new(
|
||||
peer,
|
||||
CollationFetchingRequest {
|
||||
relay_parent: old_relay_parent,
|
||||
para_id: test_state.para_id,
|
||||
},
|
||||
tx,
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
// Re-requesting collation should fail:
|
||||
rx.await.unwrap_err();
|
||||
|
||||
@@ -630,13 +621,12 @@ fn advertise_and_send_collation() {
|
||||
// Send info about peer's view.
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::PeerViewChange(
|
||||
peer.clone(),
|
||||
view![test_state.relay_parent],
|
||||
)
|
||||
)
|
||||
).await;
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerViewChange(
|
||||
peer.clone(),
|
||||
view![test_state.relay_parent],
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
expect_advertise_collation_msg(&mut virtual_overseer, &peer, test_state.relay_parent).await;
|
||||
virtual_overseer
|
||||
@@ -646,29 +636,26 @@ fn advertise_and_send_collation() {
|
||||
#[test]
|
||||
fn send_only_one_collation_per_relay_parent_at_a_time() {
|
||||
test_validator_send_sequence(|mut second_response_receiver, feedback_first_tx| async move {
|
||||
Delay::new(Duration::from_millis(100)).await;
|
||||
assert!(
|
||||
second_response_receiver.try_recv().unwrap().is_none(),
|
||||
"We should not have send the collation yet to the second validator",
|
||||
);
|
||||
Delay::new(Duration::from_millis(100)).await;
|
||||
assert!(
|
||||
second_response_receiver.try_recv().unwrap().is_none(),
|
||||
"We should not have send the collation yet to the second validator",
|
||||
);
|
||||
|
||||
// Signal that the collation fetch is finished
|
||||
feedback_first_tx.send(()).expect("Sending collation fetch finished");
|
||||
second_response_receiver
|
||||
}
|
||||
);
|
||||
// Signal that the collation fetch is finished
|
||||
feedback_first_tx.send(()).expect("Sending collation fetch finished");
|
||||
second_response_receiver
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_next_collation_after_max_unshared_upload_time() {
|
||||
test_validator_send_sequence(|second_response_receiver, _| async move {
|
||||
Delay::new(MAX_UNSHARED_UPLOAD_TIME + Duration::from_millis(50)).await;
|
||||
second_response_receiver
|
||||
}
|
||||
);
|
||||
Delay::new(MAX_UNSHARED_UPLOAD_TIME + Duration::from_millis(50)).await;
|
||||
second_response_receiver
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn collators_declare_to_connected_peers() {
|
||||
let test_state = TestState::default();
|
||||
@@ -721,7 +708,8 @@ fn collations_are_only_advertised_to_validators_with_correct_view() {
|
||||
|
||||
distribute_collation(&mut virtual_overseer, &test_state, true).await;
|
||||
|
||||
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;
|
||||
|
||||
// The other validator announces that it changed its view.
|
||||
send_peer_view_change(&mut virtual_overseer, &peer, vec![test_state.relay_parent]).await;
|
||||
@@ -772,7 +760,8 @@ fn collate_on_two_different_relay_chain_blocks() {
|
||||
|
||||
send_peer_view_change(&mut virtual_overseer, &peer2, vec![test_state.relay_parent]).await;
|
||||
|
||||
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;
|
||||
virtual_overseer
|
||||
})
|
||||
}
|
||||
@@ -833,17 +822,16 @@ fn collators_reject_declare_messages() {
|
||||
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer.clone(),
|
||||
protocol_v1::CollatorProtocolMessage::Declare(
|
||||
collator_pair2.public(),
|
||||
ParaId::from(5),
|
||||
collator_pair2.sign(b"garbage"),
|
||||
),
|
||||
)
|
||||
)
|
||||
).await;
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerMessage(
|
||||
peer.clone(),
|
||||
protocol_v1::CollatorProtocolMessage::Declare(
|
||||
collator_pair2.public(),
|
||||
ParaId::from(5),
|
||||
collator_pair2.sign(b"garbage"),
|
||||
),
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_matches!(
|
||||
overseer_recv(&mut virtual_overseer).await,
|
||||
@@ -868,7 +856,7 @@ fn collators_reject_declare_messages() {
|
||||
fn test_validator_send_sequence<T, F>(handle_first_response: T)
|
||||
where
|
||||
T: FnOnce(oneshot::Receiver<sc_network::config::OutgoingResponse>, oneshot::Sender<()>) -> F,
|
||||
F: Future<Output=oneshot::Receiver<sc_network::config::OutgoingResponse>>
|
||||
F: Future<Output = oneshot::Receiver<sc_network::config::OutgoingResponse>>,
|
||||
{
|
||||
let test_state = TestState::default();
|
||||
let local_peer_id = test_state.local_peer_id.clone();
|
||||
@@ -882,7 +870,8 @@ where
|
||||
let DistributeCollation { candidate, pov_block } =
|
||||
distribute_collation(&mut virtual_overseer, &test_state, true).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()
|
||||
.zip(test_state.current_group_validator_peer_ids())
|
||||
{
|
||||
@@ -900,29 +889,40 @@ where
|
||||
let validator_1 = test_state.current_group_validator_peer_ids()[1].clone();
|
||||
|
||||
// Send info about peer's view.
|
||||
send_peer_view_change(&mut virtual_overseer, &validator_0, vec![test_state.relay_parent]).await;
|
||||
send_peer_view_change(&mut virtual_overseer, &validator_1, vec![test_state.relay_parent]).await;
|
||||
send_peer_view_change(&mut virtual_overseer, &validator_0, vec![test_state.relay_parent])
|
||||
.await;
|
||||
send_peer_view_change(&mut virtual_overseer, &validator_1, vec![test_state.relay_parent])
|
||||
.await;
|
||||
|
||||
// The peer is interested in a leaf that we have a collation for;
|
||||
// advertise it.
|
||||
expect_advertise_collation_msg(&mut virtual_overseer, &validator_0, test_state.relay_parent).await;
|
||||
expect_advertise_collation_msg(&mut virtual_overseer, &validator_1, test_state.relay_parent).await;
|
||||
expect_advertise_collation_msg(
|
||||
&mut virtual_overseer,
|
||||
&validator_0,
|
||||
test_state.relay_parent,
|
||||
)
|
||||
.await;
|
||||
expect_advertise_collation_msg(
|
||||
&mut virtual_overseer,
|
||||
&validator_1,
|
||||
test_state.relay_parent,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Request a collation.
|
||||
let (tx, rx) = oneshot::channel();
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::CollationFetchingRequest(
|
||||
IncomingRequest::new(
|
||||
validator_0,
|
||||
CollationFetchingRequest {
|
||||
relay_parent: test_state.relay_parent,
|
||||
para_id: test_state.para_id,
|
||||
},
|
||||
tx,
|
||||
)
|
||||
)
|
||||
).await;
|
||||
CollatorProtocolMessage::CollationFetchingRequest(IncomingRequest::new(
|
||||
validator_0,
|
||||
CollationFetchingRequest {
|
||||
relay_parent: test_state.relay_parent,
|
||||
para_id: test_state.para_id,
|
||||
},
|
||||
tx,
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Keep the feedback channel alive because we need to use it to inform about the finished transfer.
|
||||
let feedback_tx = assert_matches!(
|
||||
@@ -945,17 +945,16 @@ where
|
||||
let (tx, rx) = oneshot::channel();
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::CollationFetchingRequest(
|
||||
IncomingRequest::new(
|
||||
validator_1,
|
||||
CollationFetchingRequest {
|
||||
relay_parent: test_state.relay_parent,
|
||||
para_id: test_state.para_id,
|
||||
},
|
||||
tx,
|
||||
)
|
||||
)
|
||||
).await;
|
||||
CollatorProtocolMessage::CollationFetchingRequest(IncomingRequest::new(
|
||||
validator_1,
|
||||
CollationFetchingRequest {
|
||||
relay_parent: test_state.relay_parent,
|
||||
para_id: test_state.para_id,
|
||||
},
|
||||
tx,
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
let rx = handle_first_response(rx, feedback_tx).await;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ use polkadot_node_primitives::UncheckedSignedFullStatement;
|
||||
use polkadot_subsystem::errors::SubsystemError;
|
||||
use thiserror::Error;
|
||||
|
||||
use polkadot_node_subsystem_util::{Fault, runtime, unwrap_non_fatal};
|
||||
use polkadot_node_subsystem_util::{runtime, unwrap_non_fatal, Fault};
|
||||
|
||||
use crate::LOG_TARGET;
|
||||
|
||||
@@ -82,9 +82,7 @@ pub enum NonFatal {
|
||||
///
|
||||
/// 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<()>
|
||||
{
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
//! This subsystem implements both sides of the collator protocol.
|
||||
|
||||
#![deny(missing_docs, unused_crate_dependencies)]
|
||||
#![recursion_limit="256"]
|
||||
#![recursion_limit = "256"]
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -30,14 +30,9 @@ use polkadot_node_network_protocol::{PeerId, UnifiedReputationChange as Rep};
|
||||
use polkadot_primitives::v1::CollatorPair;
|
||||
|
||||
use polkadot_subsystem::{
|
||||
SpawnedSubsystem,
|
||||
SubsystemContext,
|
||||
SubsystemSender,
|
||||
overseer,
|
||||
messages::{
|
||||
CollatorProtocolMessage, NetworkBridgeMessage,
|
||||
},
|
||||
errors::SubsystemError,
|
||||
messages::{CollatorProtocolMessage, NetworkBridgeMessage},
|
||||
overseer, SpawnedSubsystem, SubsystemContext, SubsystemSender,
|
||||
};
|
||||
|
||||
mod error;
|
||||
@@ -92,29 +87,19 @@ impl CollatorProtocolSubsystem {
|
||||
/// If `id` is `None` this is a validator side of the protocol.
|
||||
/// Caller must provide a registry for prometheus metrics.
|
||||
pub fn new(protocol_side: ProtocolSide) -> Self {
|
||||
Self {
|
||||
protocol_side,
|
||||
}
|
||||
Self { protocol_side }
|
||||
}
|
||||
|
||||
async fn run<Context>(self, ctx: Context) -> Result<()>
|
||||
where
|
||||
Context: overseer::SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
Context: overseer::SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
{
|
||||
match self.protocol_side {
|
||||
ProtocolSide::Validator { keystore, eviction_policy, metrics } => validator_side::run(
|
||||
ctx,
|
||||
keystore,
|
||||
eviction_policy,
|
||||
metrics,
|
||||
).await,
|
||||
ProtocolSide::Collator(local_peer_id, collator_pair, metrics) => collator_side::run(
|
||||
ctx,
|
||||
local_peer_id,
|
||||
collator_pair,
|
||||
metrics,
|
||||
).await,
|
||||
ProtocolSide::Validator { keystore, eviction_policy, metrics } =>
|
||||
validator_side::run(ctx, keystore, eviction_policy, metrics).await,
|
||||
ProtocolSide::Collator(local_peer_id, collator_pair, metrics) =>
|
||||
collator_side::run(ctx, local_peer_id, collator_pair, metrics).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,10 +116,7 @@ where
|
||||
.map_err(|e| SubsystemError::with_origin("collator-protocol", e))
|
||||
.boxed();
|
||||
|
||||
SpawnedSubsystem {
|
||||
name: "collator-protocol-subsystem",
|
||||
future,
|
||||
}
|
||||
SpawnedSubsystem { name: "collator-protocol-subsystem", future }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +132,5 @@ where
|
||||
"reputation change for peer",
|
||||
);
|
||||
|
||||
ctx.send_message(
|
||||
NetworkBridgeMessage::ReportPeer(peer, rep),
|
||||
).await;
|
||||
ctx.send_message(NetworkBridgeMessage::ReportPeer(peer, rep)).await;
|
||||
}
|
||||
|
||||
@@ -14,39 +14,44 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{collections::{HashMap, HashSet}, sync::Arc, task::Poll};
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::time::{Duration, Instant};
|
||||
use always_assert::never;
|
||||
use futures::{
|
||||
channel::oneshot, future::{BoxFuture, Fuse, FusedFuture}, FutureExt, StreamExt,
|
||||
stream::FuturesUnordered, select,
|
||||
channel::oneshot,
|
||||
future::{BoxFuture, Fuse, FusedFuture},
|
||||
select,
|
||||
stream::FuturesUnordered,
|
||||
FutureExt, StreamExt,
|
||||
};
|
||||
use futures_timer::Delay;
|
||||
use std::{
|
||||
collections::{hash_map::Entry, HashMap, HashSet},
|
||||
sync::Arc,
|
||||
task::Poll,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use sp_keystore::SyncCryptoStorePtr;
|
||||
|
||||
use polkadot_node_network_protocol::{
|
||||
request_response as req_res, v1 as protocol_v1,
|
||||
peer_set::PeerSet,
|
||||
request_response as req_res,
|
||||
request_response::{
|
||||
request::{Recipient, RequestError},
|
||||
v1::{CollationFetchingRequest, CollationFetchingResponse},
|
||||
OutgoingRequest, Requests,
|
||||
},
|
||||
OurView, PeerId, UnifiedReputationChange as Rep, View,
|
||||
v1 as protocol_v1, OurView, PeerId, UnifiedReputationChange as Rep, View,
|
||||
};
|
||||
use polkadot_node_primitives::{SignedFullStatement, PoV};
|
||||
use polkadot_node_primitives::{PoV, SignedFullStatement};
|
||||
use polkadot_node_subsystem_util::metrics::{self, prometheus};
|
||||
use polkadot_primitives::v1::{CandidateReceipt, CollatorId, Hash, Id as ParaId};
|
||||
use polkadot_subsystem::{
|
||||
overseer,
|
||||
jaeger,
|
||||
messages::{
|
||||
CollatorProtocolMessage, IfDisconnected,
|
||||
NetworkBridgeEvent, NetworkBridgeMessage, CandidateBackingMessage,
|
||||
CandidateBackingMessage, CollatorProtocolMessage, IfDisconnected, NetworkBridgeEvent,
|
||||
NetworkBridgeMessage,
|
||||
},
|
||||
FromOverseer, OverseerSignal, PerLeafSpan, SubsystemContext, SubsystemSender,
|
||||
overseer, FromOverseer, OverseerSignal, PerLeafSpan, SubsystemContext, SubsystemSender,
|
||||
};
|
||||
|
||||
use super::{modify_reputation, Result, LOG_TARGET};
|
||||
@@ -64,7 +69,8 @@ const COST_INVALID_SIGNATURE: Rep = Rep::Malicious("Invalid network message sign
|
||||
const COST_REPORT_BAD: Rep = Rep::Malicious("A collator was reported by another subsystem");
|
||||
const COST_WRONG_PARA: Rep = Rep::Malicious("A collator provided a collation for the wrong para");
|
||||
const COST_UNNEEDED_COLLATOR: Rep = Rep::CostMinor("An unneeded collator connected");
|
||||
const BENEFIT_NOTIFY_GOOD: Rep = Rep::BenefitMinor("A collator was noted good by another subsystem");
|
||||
const BENEFIT_NOTIFY_GOOD: Rep =
|
||||
Rep::BenefitMinor("A collator was noted good by another subsystem");
|
||||
|
||||
/// Time after starting a collation download from a collator we will start another one from the
|
||||
/// next collator even if the upload was not finished yet.
|
||||
@@ -105,13 +111,19 @@ impl Metrics {
|
||||
}
|
||||
|
||||
/// Provide a timer for `handle_collation_request_result` which observes on drop.
|
||||
fn time_handle_collation_request_result(&self) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
|
||||
self.0.as_ref().map(|metrics| metrics.handle_collation_request_result.start_timer())
|
||||
fn time_handle_collation_request_result(
|
||||
&self,
|
||||
) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
|
||||
self.0
|
||||
.as_ref()
|
||||
.map(|metrics| metrics.handle_collation_request_result.start_timer())
|
||||
}
|
||||
|
||||
/// Note the current number of collator peers.
|
||||
fn note_collator_peer_count(&self, collator_peers: usize) {
|
||||
self.0.as_ref().map(|metrics| metrics.collator_peer_count.set(collator_peers as u64));
|
||||
self.0
|
||||
.as_ref()
|
||||
.map(|metrics| metrics.collator_peer_count.set(collator_peers as u64));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,9 +136,9 @@ struct MetricsInner {
|
||||
}
|
||||
|
||||
impl metrics::Metrics for Metrics {
|
||||
fn try_register(registry: &prometheus::Registry)
|
||||
-> std::result::Result<Self, prometheus::PrometheusError>
|
||||
{
|
||||
fn try_register(
|
||||
registry: &prometheus::Registry,
|
||||
) -> std::result::Result<Self, prometheus::PrometheusError> {
|
||||
let metrics = MetricsInner {
|
||||
collation_requests: prometheus::register(
|
||||
prometheus::CounterVec::new(
|
||||
@@ -210,10 +222,7 @@ struct PeerData {
|
||||
|
||||
impl PeerData {
|
||||
fn new(view: View) -> Self {
|
||||
PeerData {
|
||||
view,
|
||||
state: PeerState::Connected(Instant::now()),
|
||||
}
|
||||
PeerData { view, state: PeerState::Connected(Instant::now()) }
|
||||
}
|
||||
|
||||
/// Update the view, clearing all advertisements that are no longer in the
|
||||
@@ -241,20 +250,17 @@ impl PeerData {
|
||||
&mut self,
|
||||
on_relay_parent: Hash,
|
||||
our_view: &View,
|
||||
)
|
||||
-> std::result::Result<(CollatorId, ParaId), AdvertisementError>
|
||||
{
|
||||
) -> std::result::Result<(CollatorId, ParaId), AdvertisementError> {
|
||||
match self.state {
|
||||
PeerState::Connected(_) => Err(AdvertisementError::UndeclaredCollator),
|
||||
_ if !our_view.contains(&on_relay_parent) => Err(AdvertisementError::OutOfOurView),
|
||||
PeerState::Collating(ref mut state) => {
|
||||
PeerState::Collating(ref mut state) =>
|
||||
if state.advertisements.insert(on_relay_parent) {
|
||||
state.last_active = Instant::now();
|
||||
Ok((state.collator_id.clone(), state.para_id.clone()))
|
||||
} else {
|
||||
Err(AdvertisementError::Duplicate)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,7 +311,8 @@ impl PeerData {
|
||||
fn is_inactive(&self, policy: &crate::CollatorEvictionPolicy) -> bool {
|
||||
match self.state {
|
||||
PeerState::Connected(connected_at) => connected_at.elapsed() >= policy.undeclared,
|
||||
PeerState::Collating(ref state) => state.last_active.elapsed() >= policy.inactive_collator,
|
||||
PeerState::Collating(ref state) =>
|
||||
state.last_active.elapsed() >= policy.inactive_collator,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -325,7 +332,7 @@ struct GroupAssignments {
|
||||
struct ActiveParas {
|
||||
relay_parent_assignments: HashMap<Hash, GroupAssignments>,
|
||||
current_assignments: HashMap<ParaId, usize>,
|
||||
next_assignments: HashMap<ParaId, usize>
|
||||
next_assignments: HashMap<ParaId, usize>,
|
||||
}
|
||||
|
||||
impl ActiveParas {
|
||||
@@ -350,7 +357,6 @@ impl ActiveParas {
|
||||
.map(|x| x.ok())
|
||||
.flatten();
|
||||
|
||||
|
||||
let mc = polkadot_node_subsystem_util::request_availability_cores(relay_parent, sender)
|
||||
.await
|
||||
.await
|
||||
@@ -368,38 +374,32 @@ impl ActiveParas {
|
||||
);
|
||||
|
||||
continue
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let (para_now, para_next) = match polkadot_node_subsystem_util
|
||||
::signing_key_and_index(&validators, keystore)
|
||||
.await
|
||||
.and_then(|(_, index)| polkadot_node_subsystem_util::find_validator_group(
|
||||
&groups,
|
||||
index,
|
||||
))
|
||||
{
|
||||
Some(group) => {
|
||||
let next_rotation_info = rotation_info.bump_rotation();
|
||||
let (para_now, para_next) =
|
||||
match polkadot_node_subsystem_util::signing_key_and_index(&validators, keystore)
|
||||
.await
|
||||
.and_then(|(_, index)| {
|
||||
polkadot_node_subsystem_util::find_validator_group(&groups, index)
|
||||
}) {
|
||||
Some(group) => {
|
||||
let next_rotation_info = rotation_info.bump_rotation();
|
||||
|
||||
let core_now = rotation_info.core_for_group(group, cores.len());
|
||||
let core_next = next_rotation_info.core_for_group(group, cores.len());
|
||||
let core_now = rotation_info.core_for_group(group, cores.len());
|
||||
let core_next = next_rotation_info.core_for_group(group, cores.len());
|
||||
|
||||
(
|
||||
cores.get(core_now.0 as usize).and_then(|c| c.para_id()),
|
||||
cores.get(core_next.0 as usize).and_then(|c| c.para_id()),
|
||||
)
|
||||
}
|
||||
None => {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
?relay_parent,
|
||||
"Not a validator",
|
||||
);
|
||||
(
|
||||
cores.get(core_now.0 as usize).and_then(|c| c.para_id()),
|
||||
cores.get(core_next.0 as usize).and_then(|c| c.para_id()),
|
||||
)
|
||||
},
|
||||
None => {
|
||||
tracing::trace!(target: LOG_TARGET, ?relay_parent, "Not a validator",);
|
||||
|
||||
continue
|
||||
}
|
||||
};
|
||||
continue
|
||||
},
|
||||
};
|
||||
|
||||
// This code won't work well, if at all for parathreads. For parathreads we'll
|
||||
// have to be aware of which core the parathread claim is going to be multiplexed
|
||||
@@ -426,17 +426,12 @@ impl ActiveParas {
|
||||
*self.next_assignments.entry(para_next).or_default() += 1;
|
||||
}
|
||||
|
||||
self.relay_parent_assignments.insert(
|
||||
relay_parent,
|
||||
GroupAssignments { current: para_now, next: para_next },
|
||||
);
|
||||
self.relay_parent_assignments
|
||||
.insert(relay_parent, GroupAssignments { current: para_now, next: para_next });
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_outgoing(
|
||||
&mut self,
|
||||
old_relay_parents: impl IntoIterator<Item = Hash>,
|
||||
) {
|
||||
fn remove_outgoing(&mut self, old_relay_parents: impl IntoIterator<Item = Hash>) {
|
||||
for old_relay_parent in old_relay_parents {
|
||||
if let Some(assignments) = self.relay_parent_assignments.remove(&old_relay_parent) {
|
||||
let GroupAssignments { current, next } = assignments;
|
||||
@@ -482,16 +477,19 @@ struct PendingCollation {
|
||||
|
||||
impl PendingCollation {
|
||||
fn new(relay_parent: Hash, para_id: &ParaId, peer_id: &PeerId) -> Self {
|
||||
Self { relay_parent, para_id: para_id.clone(), peer_id: peer_id.clone(), commitments_hash: None }
|
||||
Self {
|
||||
relay_parent,
|
||||
para_id: para_id.clone(),
|
||||
peer_id: peer_id.clone(),
|
||||
commitments_hash: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type CollationEvent = (CollatorId, PendingCollation);
|
||||
|
||||
type PendingCollationFetch = (
|
||||
CollationEvent,
|
||||
std::result::Result<(CandidateReceipt, PoV), oneshot::Canceled>,
|
||||
);
|
||||
type PendingCollationFetch =
|
||||
(CollationEvent, std::result::Result<(CandidateReceipt, PoV), oneshot::Canceled>);
|
||||
|
||||
/// The status of the collations in [`CollationsPerRelayParent`].
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -567,7 +565,7 @@ impl CollationsPerRelayParent {
|
||||
let next = self.unfetched_collations.pop();
|
||||
self.waiting_collation = next.as_ref().map(|(_, collator_id)| collator_id.clone());
|
||||
next
|
||||
}
|
||||
},
|
||||
CollationStatus::WaitingOnValidation | CollationStatus::Fetching =>
|
||||
unreachable!("We have reset the status above!"),
|
||||
}
|
||||
@@ -622,20 +620,18 @@ fn collator_peer_id(
|
||||
peer_data: &HashMap<PeerId, PeerData>,
|
||||
collator_id: &CollatorId,
|
||||
) -> Option<PeerId> {
|
||||
peer_data.iter()
|
||||
.find_map(|(peer, data)|
|
||||
data.collator_id().filter(|c| c == &collator_id).map(|_| peer.clone())
|
||||
)
|
||||
peer_data.iter().find_map(|(peer, data)| {
|
||||
data.collator_id().filter(|c| c == &collator_id).map(|_| peer.clone())
|
||||
})
|
||||
}
|
||||
|
||||
async fn disconnect_peer<Context>(ctx: &mut Context, peer_id: PeerId)
|
||||
where
|
||||
Context: overseer::SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
Context: overseer::SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
{
|
||||
ctx.send_message(
|
||||
NetworkBridgeMessage::DisconnectPeer(peer_id, PeerSet::Collation)
|
||||
).await
|
||||
ctx.send_message(NetworkBridgeMessage::DisconnectPeer(peer_id, PeerSet::Collation))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Another subsystem has requested to fetch collations on a particular leaf for some para.
|
||||
@@ -644,10 +640,9 @@ async fn fetch_collation<Context>(
|
||||
state: &mut State,
|
||||
pc: PendingCollation,
|
||||
id: CollatorId,
|
||||
)
|
||||
where
|
||||
Context: overseer::SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
) where
|
||||
Context: overseer::SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
{
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
@@ -657,7 +652,9 @@ where
|
||||
Delay::new(MAX_UNSHARED_DOWNLOAD_TIME).await;
|
||||
(collator_id, relay_parent)
|
||||
};
|
||||
state.collation_fetch_timeouts.push(timeout(id.clone(), relay_parent.clone()).boxed());
|
||||
state
|
||||
.collation_fetch_timeouts
|
||||
.push(timeout(id.clone(), relay_parent.clone()).boxed());
|
||||
|
||||
if state.peer_data.get(&peer_id).map_or(false, |d| d.has_advertised(&relay_parent)) {
|
||||
request_collation(ctx, state, relay_parent, para_id, peer_id, tx).await;
|
||||
@@ -671,9 +668,8 @@ async fn report_collator<Context>(
|
||||
ctx: &mut Context,
|
||||
peer_data: &HashMap<PeerId, PeerData>,
|
||||
id: CollatorId,
|
||||
)
|
||||
where
|
||||
Context: SubsystemContext<Message = CollatorProtocolMessage>
|
||||
) where
|
||||
Context: SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
{
|
||||
if let Some(peer_id) = collator_peer_id(peer_data, &id) {
|
||||
modify_reputation(ctx, peer_id, COST_REPORT_BAD).await;
|
||||
@@ -685,10 +681,9 @@ async fn note_good_collation<Context>(
|
||||
ctx: &mut Context,
|
||||
peer_data: &HashMap<PeerId, PeerData>,
|
||||
id: CollatorId,
|
||||
)
|
||||
where
|
||||
Context: overseer::SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
) where
|
||||
Context: overseer::SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
{
|
||||
if let Some(peer_id) = collator_peer_id(peer_data, &id) {
|
||||
modify_reputation(ctx, peer_id, BENEFIT_NOTIFY_GOOD).await;
|
||||
@@ -701,18 +696,17 @@ async fn notify_collation_seconded<Context>(
|
||||
peer_id: PeerId,
|
||||
relay_parent: Hash,
|
||||
statement: SignedFullStatement,
|
||||
)
|
||||
where
|
||||
Context: overseer::SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
) where
|
||||
Context: overseer::SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
{
|
||||
let wire_message = protocol_v1::CollatorProtocolMessage::CollationSeconded(relay_parent, statement.into());
|
||||
ctx.send_message(
|
||||
NetworkBridgeMessage::SendCollationMessage(
|
||||
vec![peer_id],
|
||||
protocol_v1::CollationProtocol::CollatorProtocol(wire_message),
|
||||
)
|
||||
).await;
|
||||
let wire_message =
|
||||
protocol_v1::CollatorProtocolMessage::CollationSeconded(relay_parent, statement.into());
|
||||
ctx.send_message(NetworkBridgeMessage::SendCollationMessage(
|
||||
vec![peer_id],
|
||||
protocol_v1::CollationProtocol::CollatorProtocol(wire_message),
|
||||
))
|
||||
.await;
|
||||
|
||||
modify_reputation(ctx, peer_id, BENEFIT_NOTIFY_GOOD).await;
|
||||
}
|
||||
@@ -720,15 +714,12 @@ where
|
||||
/// A peer's view has changed. A number of things should be done:
|
||||
/// - Ongoing collation requests have to be canceled.
|
||||
/// - Advertisements by this peer that are no longer relevant have to be removed.
|
||||
async fn handle_peer_view_change(
|
||||
state: &mut State,
|
||||
peer_id: PeerId,
|
||||
view: View,
|
||||
) -> Result<()> {
|
||||
async fn handle_peer_view_change(state: &mut State, peer_id: PeerId, view: View) -> Result<()> {
|
||||
let peer_data = state.peer_data.entry(peer_id.clone()).or_default();
|
||||
|
||||
peer_data.update_view(view);
|
||||
state.requested_collations
|
||||
state
|
||||
.requested_collations
|
||||
.retain(|pc, _| pc.peer_id != peer_id || !peer_data.has_advertised(&pc.relay_parent));
|
||||
|
||||
Ok(())
|
||||
@@ -747,10 +738,9 @@ async fn request_collation<Context>(
|
||||
para_id: ParaId,
|
||||
peer_id: PeerId,
|
||||
result: oneshot::Sender<(CandidateReceipt, PoV)>,
|
||||
)
|
||||
where
|
||||
Context: overseer::SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
) where
|
||||
Context: overseer::SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
{
|
||||
if !state.view.contains(&relay_parent) {
|
||||
tracing::debug!(
|
||||
@@ -760,7 +750,7 @@ where
|
||||
relay_parent = %relay_parent,
|
||||
"collation is no longer in view",
|
||||
);
|
||||
return;
|
||||
return
|
||||
}
|
||||
let pending_collation = PendingCollation::new(relay_parent, ¶_id, &peer_id);
|
||||
if state.requested_collations.contains_key(&pending_collation) {
|
||||
@@ -771,29 +761,27 @@ where
|
||||
?pending_collation.relay_parent,
|
||||
"collation has already been requested",
|
||||
);
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
let (full_request, response_recv) =
|
||||
OutgoingRequest::new(Recipient::Peer(peer_id), CollationFetchingRequest {
|
||||
relay_parent,
|
||||
para_id,
|
||||
});
|
||||
let (full_request, response_recv) = OutgoingRequest::new(
|
||||
Recipient::Peer(peer_id),
|
||||
CollationFetchingRequest { relay_parent, para_id },
|
||||
);
|
||||
let requests = Requests::CollationFetching(full_request);
|
||||
|
||||
let per_request = PerRequest {
|
||||
from_collator: response_recv.boxed().fuse(),
|
||||
to_requester: result,
|
||||
span: state.span_per_relay_parent.get(&relay_parent).map(|s| {
|
||||
s.child("collation-request")
|
||||
.with_para_id(para_id)
|
||||
}),
|
||||
span: state
|
||||
.span_per_relay_parent
|
||||
.get(&relay_parent)
|
||||
.map(|s| s.child("collation-request").with_para_id(para_id)),
|
||||
};
|
||||
|
||||
state.requested_collations.insert(
|
||||
PendingCollation::new(relay_parent, ¶_id, &peer_id),
|
||||
per_request
|
||||
);
|
||||
state
|
||||
.requested_collations
|
||||
.insert(PendingCollation::new(relay_parent, ¶_id, &peer_id), per_request);
|
||||
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
@@ -803,9 +791,11 @@ where
|
||||
"Requesting collation",
|
||||
);
|
||||
|
||||
ctx.send_message(
|
||||
NetworkBridgeMessage::SendRequests(vec![requests], IfDisconnected::ImmediateError)
|
||||
).await;
|
||||
ctx.send_message(NetworkBridgeMessage::SendRequests(
|
||||
vec![requests],
|
||||
IfDisconnected::ImmediateError,
|
||||
))
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Networking message has been received.
|
||||
@@ -814,10 +804,9 @@ async fn process_incoming_peer_message<Context>(
|
||||
state: &mut State,
|
||||
origin: PeerId,
|
||||
msg: protocol_v1::CollatorProtocolMessage,
|
||||
)
|
||||
where
|
||||
Context: overseer::SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
) where
|
||||
Context: overseer::SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
{
|
||||
use protocol_v1::CollatorProtocolMessage::*;
|
||||
use sp_runtime::traits::AppVerify;
|
||||
@@ -833,7 +822,7 @@ where
|
||||
None => {
|
||||
modify_reputation(ctx, origin, COST_UNEXPECTED_MESSAGE).await;
|
||||
return
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if peer_data.is_collating() {
|
||||
@@ -869,9 +858,12 @@ where
|
||||
tracing::trace!(target: LOG_TARGET, "Disconnecting unneeded collator");
|
||||
disconnect_peer(ctx, origin).await;
|
||||
}
|
||||
}
|
||||
},
|
||||
AdvertiseCollation(relay_parent) => {
|
||||
let _span = state.span_per_relay_parent.get(&relay_parent).map(|s| s.child("advertise-collation"));
|
||||
let _span = state
|
||||
.span_per_relay_parent
|
||||
.get(&relay_parent)
|
||||
.map(|s| s.child("advertise-collation"));
|
||||
if !state.view.contains(&relay_parent) {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
@@ -881,14 +873,14 @@ where
|
||||
);
|
||||
|
||||
modify_reputation(ctx, origin, COST_UNEXPECTED_MESSAGE).await;
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
let peer_data = match state.peer_data.get_mut(&origin) {
|
||||
None => {
|
||||
modify_reputation(ctx, origin, COST_UNEXPECTED_MESSAGE).await;
|
||||
return;
|
||||
}
|
||||
return
|
||||
},
|
||||
Some(p) => p,
|
||||
};
|
||||
|
||||
@@ -902,13 +894,10 @@ where
|
||||
"Received advertise collation",
|
||||
);
|
||||
|
||||
let pending_collation = PendingCollation::new(
|
||||
relay_parent,
|
||||
¶_id,
|
||||
&origin,
|
||||
);
|
||||
let pending_collation = PendingCollation::new(relay_parent, ¶_id, &origin);
|
||||
|
||||
let collations = state.collations_per_relay_parent.entry(relay_parent).or_default();
|
||||
let collations =
|
||||
state.collations_per_relay_parent.entry(relay_parent).or_default();
|
||||
|
||||
match collations.status {
|
||||
CollationStatus::Fetching | CollationStatus::WaitingOnValidation =>
|
||||
@@ -921,7 +910,7 @@ where
|
||||
},
|
||||
CollationStatus::Seconded => {},
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
@@ -932,33 +921,26 @@ where
|
||||
);
|
||||
|
||||
modify_reputation(ctx, origin, COST_UNEXPECTED_MESSAGE).await;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
CollationSeconded(_, _) => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
peer_id = ?origin,
|
||||
"Unexpected `CollationSeconded` message, decreasing reputation",
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// A leaf has become inactive so we want to
|
||||
/// - Cancel all ongoing collation requests that are on top of that leaf.
|
||||
/// - Remove all stored collations relevant to that leaf.
|
||||
async fn remove_relay_parent(
|
||||
state: &mut State,
|
||||
relay_parent: Hash,
|
||||
) -> Result<()> {
|
||||
state.requested_collations.retain(|k, _| {
|
||||
k.relay_parent != relay_parent
|
||||
});
|
||||
async fn remove_relay_parent(state: &mut State, relay_parent: Hash) -> Result<()> {
|
||||
state.requested_collations.retain(|k, _| k.relay_parent != relay_parent);
|
||||
|
||||
state.pending_candidates.retain(|k, _| {
|
||||
k != &relay_parent
|
||||
});
|
||||
state.pending_candidates.retain(|k, _| k != &relay_parent);
|
||||
|
||||
state.collations_per_relay_parent.remove(&relay_parent);
|
||||
Ok(())
|
||||
@@ -972,12 +954,13 @@ async fn handle_our_view_change<Context>(
|
||||
view: OurView,
|
||||
) -> Result<()>
|
||||
where
|
||||
Context: overseer::SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
Context: overseer::SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
{
|
||||
let old_view = std::mem::replace(&mut state.view, view);
|
||||
|
||||
let added: HashMap<Hash, Arc<jaeger::Span>> = state.view
|
||||
let added: HashMap<Hash, Arc<jaeger::Span>> = state
|
||||
.view
|
||||
.span_per_head()
|
||||
.iter()
|
||||
.filter(|v| !old_view.contains(&v.0))
|
||||
@@ -989,10 +972,7 @@ where
|
||||
});
|
||||
|
||||
let added = state.view.difference(&old_view).cloned().collect::<Vec<_>>();
|
||||
let removed = old_view
|
||||
.difference(&state.view)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let removed = old_view.difference(&state.view).cloned().collect::<Vec<_>>();
|
||||
|
||||
for removed in removed.iter().cloned() {
|
||||
remove_relay_parent(state, removed).await?;
|
||||
@@ -1028,8 +1008,8 @@ async fn handle_network_msg<Context>(
|
||||
bridge_message: NetworkBridgeEvent<protocol_v1::CollatorProtocolMessage>,
|
||||
) -> Result<()>
|
||||
where
|
||||
Context: overseer::SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
Context: overseer::SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
{
|
||||
use NetworkBridgeEvent::*;
|
||||
|
||||
@@ -1044,7 +1024,7 @@ where
|
||||
},
|
||||
NewGossipTopology(..) => {
|
||||
// impossibru!
|
||||
}
|
||||
},
|
||||
PeerViewChange(peer_id, view) => {
|
||||
handle_peer_view_change(state, peer_id, view).await?;
|
||||
},
|
||||
@@ -1053,7 +1033,7 @@ where
|
||||
},
|
||||
PeerMessage(remote, msg) => {
|
||||
process_incoming_peer_message(ctx, state, remote, msg).await;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -1065,10 +1045,9 @@ async fn process_msg<Context>(
|
||||
keystore: &SyncCryptoStorePtr,
|
||||
msg: CollatorProtocolMessage,
|
||||
state: &mut State,
|
||||
)
|
||||
where
|
||||
Context: overseer::SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
) where
|
||||
Context: overseer::SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
{
|
||||
use CollatorProtocolMessage::*;
|
||||
|
||||
@@ -1081,36 +1060,31 @@ where
|
||||
para_id = %id,
|
||||
"CollateOn message is not expected on the validator side of the protocol",
|
||||
);
|
||||
}
|
||||
},
|
||||
DistributeCollation(_, _, _) => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
"DistributeCollation message is not expected on the validator side of the protocol",
|
||||
);
|
||||
}
|
||||
},
|
||||
ReportCollator(id) => {
|
||||
report_collator(ctx, &state.peer_data, id).await;
|
||||
}
|
||||
},
|
||||
NetworkBridgeUpdateV1(event) => {
|
||||
if let Err(e) = handle_network_msg(
|
||||
ctx,
|
||||
state,
|
||||
keystore,
|
||||
event,
|
||||
).await {
|
||||
if let Err(e) = handle_network_msg(ctx, state, keystore, event).await {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
err = ?e,
|
||||
"Failed to handle incoming network message",
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
CollationFetchingRequest(_) => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
"CollationFetchingRequest message is not expected on the validator side of the protocol",
|
||||
);
|
||||
}
|
||||
},
|
||||
Seconded(parent, stmt) => {
|
||||
if let Some(collation_event) = state.pending_candidates.remove(&parent) {
|
||||
let (collator_id, pending_collation) = collation_event;
|
||||
@@ -1128,11 +1102,13 @@ where
|
||||
"Collation has been seconded, but the relay parent is deactivated",
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
Invalid(parent, candidate_receipt) => {
|
||||
let id = match state.pending_candidates.entry(parent) {
|
||||
Entry::Occupied(entry)
|
||||
if entry.get().1.commitments_hash == Some(candidate_receipt.commitments_hash) => entry.remove().0,
|
||||
if entry.get().1.commitments_hash ==
|
||||
Some(candidate_receipt.commitments_hash) =>
|
||||
entry.remove().0,
|
||||
Entry::Occupied(_) => {
|
||||
tracing::error!(
|
||||
target: LOG_TARGET,
|
||||
@@ -1141,14 +1117,14 @@ where
|
||||
"Reported invalid candidate for unknown `pending_candidate`!",
|
||||
);
|
||||
return
|
||||
}
|
||||
},
|
||||
Entry::Vacant(_) => return,
|
||||
};
|
||||
|
||||
report_collator(ctx, &state.peer_data, id.clone()).await;
|
||||
|
||||
dequeue_next_collation_and_fetch(ctx, state, parent, id).await;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1172,20 +1148,18 @@ pub(crate) async fn run<Context>(
|
||||
metrics: Metrics,
|
||||
) -> Result<()>
|
||||
where
|
||||
Context: overseer::SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
Context: overseer::SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
{
|
||||
use OverseerSignal::*;
|
||||
|
||||
let mut state = State {
|
||||
metrics,
|
||||
..Default::default()
|
||||
};
|
||||
let mut state = State { metrics, ..Default::default() };
|
||||
|
||||
let next_inactivity_stream = futures::stream::unfold(
|
||||
Instant::now() + ACTIVITY_POLL,
|
||||
|next_check| async move { Some(((), wait_until_next_check(next_check).await)) }
|
||||
).fuse();
|
||||
let next_inactivity_stream =
|
||||
futures::stream::unfold(Instant::now() + ACTIVITY_POLL, |next_check| async move {
|
||||
Some(((), wait_until_next_check(next_check).await))
|
||||
})
|
||||
.fuse();
|
||||
|
||||
futures::pin_mut!(next_inactivity_stream);
|
||||
|
||||
@@ -1227,8 +1201,13 @@ where
|
||||
for (pending_collation, per_req) in state.requested_collations.iter_mut() {
|
||||
// Despite the await, this won't block on the response itself.
|
||||
let finished = poll_collation_response(
|
||||
&mut ctx, &state.metrics, &state.span_per_relay_parent, pending_collation, per_req,
|
||||
).await;
|
||||
&mut ctx,
|
||||
&state.metrics,
|
||||
&state.span_per_relay_parent,
|
||||
pending_collation,
|
||||
per_req,
|
||||
)
|
||||
.await;
|
||||
if !finished {
|
||||
retained_requested.insert(pending_collation.clone());
|
||||
}
|
||||
@@ -1240,13 +1219,15 @@ where
|
||||
|
||||
/// Dequeue another collation and fetch.
|
||||
async fn dequeue_next_collation_and_fetch(
|
||||
ctx: &mut (impl SubsystemContext<Message = CollatorProtocolMessage> + overseer::SubsystemContext<Message = CollatorProtocolMessage>),
|
||||
ctx: &mut (impl SubsystemContext<Message = CollatorProtocolMessage>
|
||||
+ overseer::SubsystemContext<Message = CollatorProtocolMessage>),
|
||||
state: &mut State,
|
||||
relay_parent: Hash,
|
||||
// The collator we tried to fetch from last.
|
||||
previous_fetch: CollatorId,
|
||||
) {
|
||||
if let Some((next, id)) = state.collations_per_relay_parent
|
||||
if let Some((next, id)) = state
|
||||
.collations_per_relay_parent
|
||||
.get_mut(&relay_parent)
|
||||
.and_then(|c| c.get_next_collation_to_fetch(Some(previous_fetch)))
|
||||
{
|
||||
@@ -1259,10 +1240,9 @@ async fn handle_collation_fetched_result<Context>(
|
||||
ctx: &mut Context,
|
||||
state: &mut State,
|
||||
(mut collation_event, res): PendingCollationFetch,
|
||||
)
|
||||
where
|
||||
Context: overseer::SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
) where
|
||||
Context: overseer::SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
{
|
||||
// If no prior collation for this relay parent has been seconded, then
|
||||
// memoize the collation_event for that relay_parent, such that we may
|
||||
@@ -1301,13 +1281,12 @@ where
|
||||
|
||||
if let Entry::Vacant(entry) = state.pending_candidates.entry(relay_parent) {
|
||||
collation_event.1.commitments_hash = Some(candidate_receipt.commitments_hash);
|
||||
ctx.send_message(
|
||||
CandidateBackingMessage::Second(
|
||||
relay_parent.clone(),
|
||||
candidate_receipt,
|
||||
pov,
|
||||
)
|
||||
).await;
|
||||
ctx.send_message(CandidateBackingMessage::Second(
|
||||
relay_parent.clone(),
|
||||
candidate_receipt,
|
||||
pov,
|
||||
))
|
||||
.await;
|
||||
|
||||
entry.insert(collation_event);
|
||||
} else {
|
||||
@@ -1327,10 +1306,9 @@ async fn disconnect_inactive_peers<Context>(
|
||||
ctx: &mut Context,
|
||||
eviction_policy: &crate::CollatorEvictionPolicy,
|
||||
peers: &HashMap<PeerId, PeerData>,
|
||||
)
|
||||
where
|
||||
Context: overseer::SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
) where
|
||||
Context: overseer::SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
Context: SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
{
|
||||
for (peer, peer_data) in peers {
|
||||
if peer_data.is_inactive(&eviction_policy) {
|
||||
@@ -1352,10 +1330,9 @@ async fn poll_collation_response<Context>(
|
||||
spans: &HashMap<Hash, PerLeafSpan>,
|
||||
pending_collation: &PendingCollation,
|
||||
per_req: &mut PerRequest,
|
||||
)
|
||||
-> bool
|
||||
) -> bool
|
||||
where
|
||||
Context: overseer::SubsystemContext<Message=CollatorProtocolMessage>,
|
||||
Context: overseer::SubsystemContext<Message = CollatorProtocolMessage>,
|
||||
Context: SubsystemContext,
|
||||
{
|
||||
if never!(per_req.from_collator.is_terminated()) {
|
||||
@@ -1367,8 +1344,9 @@ where
|
||||
}
|
||||
|
||||
if let Poll::Ready(response) = futures::poll!(&mut per_req.from_collator) {
|
||||
let _span = spans.get(&pending_collation.relay_parent)
|
||||
.map(|s| s.child("received-collation"));
|
||||
let _span = spans
|
||||
.get(&pending_collation.relay_parent)
|
||||
.map(|s| s.child("received-collation"));
|
||||
let _timer = metrics.time_handle_collation_request_result();
|
||||
|
||||
let mut metrics_result = Err(());
|
||||
@@ -1384,12 +1362,9 @@ where
|
||||
err = ?err,
|
||||
"Collator provided response that could not be decoded"
|
||||
);
|
||||
modify_reputation(
|
||||
ctx,
|
||||
pending_collation.peer_id.clone(),
|
||||
COST_CORRUPTED_MESSAGE
|
||||
).await;
|
||||
}
|
||||
modify_reputation(ctx, pending_collation.peer_id.clone(), COST_CORRUPTED_MESSAGE)
|
||||
.await;
|
||||
},
|
||||
Err(RequestError::NetworkError(err)) => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
@@ -1404,7 +1379,7 @@ where
|
||||
// which would result in reduced reputation for proper nodes, but the
|
||||
// same can happen for penalties on timeouts, which we also have.
|
||||
modify_reputation(ctx, pending_collation.peer_id.clone(), COST_NETWORK_ERROR).await;
|
||||
}
|
||||
},
|
||||
Err(RequestError::Canceled(_)) => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
@@ -1417,8 +1392,9 @@ where
|
||||
// sensible. In theory this could be exploited, by DoSing this node,
|
||||
// which would result in reduced reputation for proper nodes, but the
|
||||
// same can happen for penalties on timeouts, which we also have.
|
||||
modify_reputation(ctx, pending_collation.peer_id.clone(), COST_REQUEST_TIMED_OUT).await;
|
||||
}
|
||||
modify_reputation(ctx, pending_collation.peer_id.clone(), COST_REQUEST_TIMED_OUT)
|
||||
.await;
|
||||
},
|
||||
Ok(CollationFetchingResponse::Collation(receipt, _))
|
||||
if receipt.descriptor().para_id != pending_collation.para_id =>
|
||||
{
|
||||
@@ -1446,7 +1422,7 @@ where
|
||||
std::mem::swap(&mut tx, &mut (per_req.to_requester));
|
||||
let result = tx.send((receipt, pov));
|
||||
|
||||
if let Err(_) = result {
|
||||
if let Err(_) = result {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
hash = ?pending_collation.relay_parent,
|
||||
@@ -1458,7 +1434,7 @@ where
|
||||
metrics_result = Ok(());
|
||||
success = "true";
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
metrics.on_request(metrics_result);
|
||||
per_req.span.as_mut().map(|s| s.add_string_tag("success", success));
|
||||
|
||||
@@ -15,26 +15,26 @@
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
use std::{iter, time::Duration};
|
||||
use std::sync::Arc;
|
||||
use assert_matches::assert_matches;
|
||||
use futures::{executor, future, Future};
|
||||
use sp_core::{crypto::Pair, Encode};
|
||||
use sp_keystore::SyncCryptoStore;
|
||||
use sp_keystore::testing::KeyStore as TestKeyStore;
|
||||
use sp_keyring::Sr25519Keyring;
|
||||
use assert_matches::assert_matches;
|
||||
use sp_keystore::{testing::KeyStore as TestKeyStore, SyncCryptoStore};
|
||||
use std::{iter, sync::Arc, time::Duration};
|
||||
|
||||
use polkadot_primitives::v1::{
|
||||
CollatorPair, ValidatorId, ValidatorIndex, CoreState, CandidateDescriptor,
|
||||
GroupRotationInfo, ScheduledCore, OccupiedCore, GroupIndex,
|
||||
use polkadot_node_network_protocol::{
|
||||
our_view,
|
||||
request_response::{Requests, ResponseSender},
|
||||
ObservedRole,
|
||||
};
|
||||
use polkadot_node_primitives::BlockData;
|
||||
use polkadot_node_subsystem_util::TimeoutExt;
|
||||
use polkadot_subsystem_testhelpers as test_helpers;
|
||||
use polkadot_subsystem::messages::{AllMessages, RuntimeApiMessage, RuntimeApiRequest};
|
||||
use polkadot_node_network_protocol::{
|
||||
our_view, ObservedRole, request_response::{Requests, ResponseSender},
|
||||
use polkadot_primitives::v1::{
|
||||
CandidateDescriptor, CollatorPair, CoreState, GroupIndex, GroupRotationInfo, OccupiedCore,
|
||||
ScheduledCore, ValidatorId, ValidatorIndex,
|
||||
};
|
||||
use polkadot_subsystem::messages::{AllMessages, RuntimeApiMessage, RuntimeApiRequest};
|
||||
use polkadot_subsystem_testhelpers as test_helpers;
|
||||
|
||||
const ACTIVITY_TIMEOUT: Duration = Duration::from_millis(500);
|
||||
const DECLARE_TIMEOUT: Duration = Duration::from_millis(25);
|
||||
@@ -58,10 +58,7 @@ impl Default for TestState {
|
||||
|
||||
let chain_ids = vec![chain_a, chain_b];
|
||||
let relay_parent = Hash::repeat_byte(0x05);
|
||||
let collators = iter::repeat(())
|
||||
.map(|_| CollatorPair::generate().0)
|
||||
.take(5)
|
||||
.collect();
|
||||
let collators = iter::repeat(()).map(|_| CollatorPair::generate().0).take(5).collect();
|
||||
|
||||
let validators = vec![
|
||||
Sr25519Keyring::Alice,
|
||||
@@ -78,17 +75,11 @@ impl Default for TestState {
|
||||
vec![ValidatorIndex(4)],
|
||||
];
|
||||
|
||||
let group_rotation_info = GroupRotationInfo {
|
||||
session_start_block: 0,
|
||||
group_rotation_frequency: 1,
|
||||
now: 0,
|
||||
};
|
||||
let group_rotation_info =
|
||||
GroupRotationInfo { session_start_block: 0, group_rotation_frequency: 1, now: 0 };
|
||||
|
||||
let cores = vec![
|
||||
CoreState::Scheduled(ScheduledCore {
|
||||
para_id: chain_ids[0],
|
||||
collator: None,
|
||||
}),
|
||||
CoreState::Scheduled(ScheduledCore { para_id: chain_ids[0], collator: None }),
|
||||
CoreState::Free,
|
||||
CoreState::Occupied(OccupiedCore {
|
||||
next_up_on_available: None,
|
||||
@@ -129,14 +120,8 @@ struct TestHarness {
|
||||
fn test_harness<T: Future<Output = VirtualOverseer>>(test: impl FnOnce(TestHarness) -> T) {
|
||||
let _ = env_logger::builder()
|
||||
.is_test(true)
|
||||
.filter(
|
||||
Some("polkadot_collator_protocol"),
|
||||
log::LevelFilter::Trace,
|
||||
)
|
||||
.filter(
|
||||
Some(LOG_TARGET),
|
||||
log::LevelFilter::Trace,
|
||||
)
|
||||
.filter(Some("polkadot_collator_protocol"), log::LevelFilter::Trace)
|
||||
.filter(Some(LOG_TARGET), log::LevelFilter::Trace)
|
||||
.try_init();
|
||||
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
@@ -144,10 +129,12 @@ fn test_harness<T: Future<Output = VirtualOverseer>>(test: impl FnOnce(TestHarne
|
||||
let (context, virtual_overseer) = test_helpers::make_subsystem_context(pool.clone());
|
||||
|
||||
let keystore = TestKeyStore::new();
|
||||
keystore.sr25519_generate_new(
|
||||
polkadot_primitives::v1::PARACHAIN_KEY_TYPE_ID,
|
||||
Some(&Sr25519Keyring::Alice.to_seed()),
|
||||
).unwrap();
|
||||
keystore
|
||||
.sr25519_generate_new(
|
||||
polkadot_primitives::v1::PARACHAIN_KEY_TYPE_ID,
|
||||
Some(&Sr25519Keyring::Alice.to_seed()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let subsystem = run(
|
||||
context,
|
||||
@@ -164,18 +151,20 @@ fn test_harness<T: Future<Output = VirtualOverseer>>(test: impl FnOnce(TestHarne
|
||||
futures::pin_mut!(test_fut);
|
||||
futures::pin_mut!(subsystem);
|
||||
|
||||
executor::block_on(future::join(async move {
|
||||
let mut overseer = test_fut.await;
|
||||
overseer_signal(&mut overseer, OverseerSignal::Conclude).await;
|
||||
}, subsystem)).1.unwrap();
|
||||
executor::block_on(future::join(
|
||||
async move {
|
||||
let mut overseer = test_fut.await;
|
||||
overseer_signal(&mut overseer, OverseerSignal::Conclude).await;
|
||||
},
|
||||
subsystem,
|
||||
))
|
||||
.1
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
const TIMEOUT: Duration = Duration::from_millis(200);
|
||||
|
||||
async fn overseer_send(
|
||||
overseer: &mut VirtualOverseer,
|
||||
msg: CollatorProtocolMessage,
|
||||
) {
|
||||
async fn overseer_send(overseer: &mut VirtualOverseer, msg: CollatorProtocolMessage) {
|
||||
tracing::trace!("Sending message:\n{:?}", &msg);
|
||||
overseer
|
||||
.send(FromOverseer::Communication { msg })
|
||||
@@ -184,9 +173,7 @@ async fn overseer_send(
|
||||
.expect(&format!("{:?} is enough for sending messages.", TIMEOUT));
|
||||
}
|
||||
|
||||
async fn overseer_recv(
|
||||
overseer: &mut VirtualOverseer,
|
||||
) -> AllMessages {
|
||||
async fn overseer_recv(overseer: &mut VirtualOverseer) -> AllMessages {
|
||||
let msg = overseer_recv_with_timeout(overseer, TIMEOUT)
|
||||
.await
|
||||
.expect(&format!("{:?} is enough to receive messages.", TIMEOUT));
|
||||
@@ -201,16 +188,10 @@ async fn overseer_recv_with_timeout(
|
||||
timeout: Duration,
|
||||
) -> Option<AllMessages> {
|
||||
tracing::trace!("Waiting for message...");
|
||||
overseer
|
||||
.recv()
|
||||
.timeout(timeout)
|
||||
.await
|
||||
overseer.recv().timeout(timeout).await
|
||||
}
|
||||
|
||||
async fn overseer_signal(
|
||||
overseer: &mut VirtualOverseer,
|
||||
signal: OverseerSignal,
|
||||
) {
|
||||
async fn overseer_signal(overseer: &mut VirtualOverseer, signal: OverseerSignal) {
|
||||
overseer
|
||||
.send(FromOverseer::Signal(signal))
|
||||
.timeout(TIMEOUT)
|
||||
@@ -275,10 +256,7 @@ async fn assert_candidate_backing_second(
|
||||
}
|
||||
|
||||
/// Assert that a collator got disconnected.
|
||||
async fn assert_collator_disconnect(
|
||||
virtual_overseer: &mut VirtualOverseer,
|
||||
expected_peer: PeerId,
|
||||
) {
|
||||
async fn assert_collator_disconnect(virtual_overseer: &mut VirtualOverseer, expected_peer: PeerId) {
|
||||
assert_matches!(
|
||||
overseer_recv(virtual_overseer).await,
|
||||
AllMessages::NetworkBridge(NetworkBridgeMessage::DisconnectPeer(
|
||||
@@ -324,28 +302,26 @@ async fn connect_and_declare_collator(
|
||||
) {
|
||||
overseer_send(
|
||||
virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::PeerConnected(
|
||||
peer.clone(),
|
||||
ObservedRole::Full,
|
||||
None,
|
||||
),
|
||||
)
|
||||
).await;
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerConnected(
|
||||
peer.clone(),
|
||||
ObservedRole::Full,
|
||||
None,
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
overseer_send(
|
||||
virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer.clone(),
|
||||
protocol_v1::CollatorProtocolMessage::Declare(
|
||||
collator.public(),
|
||||
para_id,
|
||||
collator.sign(&protocol_v1::declare_signature_payload(&peer)),
|
||||
)
|
||||
)
|
||||
)
|
||||
).await;
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerMessage(
|
||||
peer.clone(),
|
||||
protocol_v1::CollatorProtocolMessage::Declare(
|
||||
collator.public(),
|
||||
para_id,
|
||||
collator.sign(&protocol_v1::declare_signature_payload(&peer)),
|
||||
),
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Advertise a collation.
|
||||
@@ -356,15 +332,12 @@ async fn advertise_collation(
|
||||
) {
|
||||
overseer_send(
|
||||
virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer,
|
||||
protocol_v1::CollatorProtocolMessage::AdvertiseCollation(
|
||||
relay_parent,
|
||||
)
|
||||
)
|
||||
)
|
||||
).await;
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerMessage(
|
||||
peer,
|
||||
protocol_v1::CollatorProtocolMessage::AdvertiseCollation(relay_parent),
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
// As we receive a relevant advertisement act on it and issue a collation request.
|
||||
@@ -373,29 +346,39 @@ fn act_on_advertisement() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
test_harness(|test_harness| async move {
|
||||
let TestHarness {
|
||||
mut virtual_overseer,
|
||||
} = test_harness;
|
||||
let TestHarness { mut virtual_overseer } = test_harness;
|
||||
|
||||
let pair = CollatorPair::generate().0;
|
||||
tracing::trace!("activating");
|
||||
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::OurViewChange(our_view![test_state.relay_parent])
|
||||
)
|
||||
).await;
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::OurViewChange(
|
||||
our_view![test_state.relay_parent],
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
|
||||
|
||||
let peer_b = PeerId::random();
|
||||
|
||||
connect_and_declare_collator(&mut virtual_overseer, peer_b.clone(), pair.clone(), test_state.chain_ids[0]).await;
|
||||
connect_and_declare_collator(
|
||||
&mut virtual_overseer,
|
||||
peer_b.clone(),
|
||||
pair.clone(),
|
||||
test_state.chain_ids[0],
|
||||
)
|
||||
.await;
|
||||
|
||||
advertise_collation(&mut virtual_overseer, peer_b.clone(), test_state.relay_parent).await;
|
||||
|
||||
assert_fetch_collation_request(&mut virtual_overseer, test_state.relay_parent, test_state.chain_ids[0]).await;
|
||||
assert_fetch_collation_request(
|
||||
&mut virtual_overseer,
|
||||
test_state.relay_parent,
|
||||
test_state.chain_ids[0],
|
||||
)
|
||||
.await;
|
||||
|
||||
virtual_overseer
|
||||
});
|
||||
@@ -407,16 +390,15 @@ fn collator_reporting_works() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
test_harness(|test_harness| async move {
|
||||
let TestHarness {
|
||||
mut virtual_overseer,
|
||||
} = test_harness;
|
||||
let TestHarness { mut virtual_overseer } = test_harness;
|
||||
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::OurViewChange(our_view![test_state.relay_parent])
|
||||
)
|
||||
).await;
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::OurViewChange(
|
||||
our_view![test_state.relay_parent],
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
|
||||
|
||||
@@ -428,19 +410,22 @@ fn collator_reporting_works() {
|
||||
peer_b.clone(),
|
||||
test_state.collators[0].clone(),
|
||||
test_state.chain_ids[0].clone(),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
connect_and_declare_collator(
|
||||
&mut virtual_overseer,
|
||||
peer_c.clone(),
|
||||
test_state.collators[1].clone(),
|
||||
test_state.chain_ids[0].clone(),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::ReportCollator(test_state.collators[0].public()),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_matches!(
|
||||
overseer_recv(&mut virtual_overseer).await,
|
||||
@@ -462,22 +447,19 @@ fn collator_authentication_verification_works() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
test_harness(|test_harness| async move {
|
||||
let TestHarness {
|
||||
mut virtual_overseer,
|
||||
} = test_harness;
|
||||
let TestHarness { mut virtual_overseer } = test_harness;
|
||||
|
||||
let peer_b = PeerId::random();
|
||||
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::PeerConnected(
|
||||
peer_b,
|
||||
ObservedRole::Full,
|
||||
None,
|
||||
),
|
||||
)
|
||||
).await;
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerConnected(
|
||||
peer_b,
|
||||
ObservedRole::Full,
|
||||
None,
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
// the peer sends a declare message but sign the wrong payload
|
||||
overseer_send(
|
||||
@@ -521,18 +503,17 @@ fn fetch_collations_works() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
test_harness(|test_harness| async move {
|
||||
let TestHarness {
|
||||
mut virtual_overseer,
|
||||
} = test_harness;
|
||||
let TestHarness { mut virtual_overseer } = test_harness;
|
||||
|
||||
let second = Hash::random();
|
||||
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::OurViewChange(our_view![test_state.relay_parent, second])
|
||||
),
|
||||
).await;
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::OurViewChange(
|
||||
our_view![test_state.relay_parent, second],
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
|
||||
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
|
||||
@@ -545,14 +526,16 @@ fn fetch_collations_works() {
|
||||
peer_b.clone(),
|
||||
test_state.collators[0].clone(),
|
||||
test_state.chain_ids[0].clone(),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
connect_and_declare_collator(
|
||||
&mut virtual_overseer,
|
||||
peer_c.clone(),
|
||||
test_state.collators[1].clone(),
|
||||
test_state.chain_ids[0].clone(),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
advertise_collation(&mut virtual_overseer, peer_b.clone(), test_state.relay_parent).await;
|
||||
advertise_collation(&mut virtual_overseer, peer_c.clone(), test_state.relay_parent).await;
|
||||
@@ -561,7 +544,8 @@ fn fetch_collations_works() {
|
||||
&mut virtual_overseer,
|
||||
test_state.relay_parent,
|
||||
test_state.chain_ids[0],
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(
|
||||
overseer_recv_with_timeout(&mut &mut virtual_overseer, Duration::from_millis(30)).await.is_none(),
|
||||
@@ -572,29 +556,35 @@ fn fetch_collations_works() {
|
||||
let mut candidate_a = CandidateReceipt::default();
|
||||
candidate_a.descriptor.para_id = test_state.chain_ids[0];
|
||||
candidate_a.descriptor.relay_parent = test_state.relay_parent;
|
||||
response_channel.send(Ok(
|
||||
CollationFetchingResponse::Collation(
|
||||
candidate_a.clone(),
|
||||
pov.clone(),
|
||||
).encode()
|
||||
)).expect("Sending response should succeed");
|
||||
response_channel
|
||||
.send(Ok(
|
||||
CollationFetchingResponse::Collation(candidate_a.clone(), pov.clone()).encode()
|
||||
))
|
||||
.expect("Sending response should succeed");
|
||||
|
||||
assert_candidate_backing_second(
|
||||
&mut virtual_overseer,
|
||||
test_state.relay_parent,
|
||||
test_state.chain_ids[0],
|
||||
&pov,
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerDisconnected(peer_b.clone())),
|
||||
).await;
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerDisconnected(
|
||||
peer_b.clone(),
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerDisconnected(peer_c.clone())),
|
||||
).await;
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerDisconnected(
|
||||
peer_c.clone(),
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
let peer_b = PeerId::random();
|
||||
let peer_c = PeerId::random();
|
||||
@@ -605,32 +595,32 @@ fn fetch_collations_works() {
|
||||
peer_b.clone(),
|
||||
test_state.collators[2].clone(),
|
||||
test_state.chain_ids[0].clone(),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
connect_and_declare_collator(
|
||||
&mut virtual_overseer,
|
||||
peer_c.clone(),
|
||||
test_state.collators[3].clone(),
|
||||
test_state.chain_ids[0].clone(),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
connect_and_declare_collator(
|
||||
&mut virtual_overseer,
|
||||
peer_d.clone(),
|
||||
test_state.collators[4].clone(),
|
||||
test_state.chain_ids[0].clone(),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
advertise_collation(&mut virtual_overseer, peer_b.clone(), second).await;
|
||||
advertise_collation(&mut virtual_overseer, peer_c.clone(), second).await;
|
||||
advertise_collation(&mut virtual_overseer, peer_d.clone(), second).await;
|
||||
|
||||
// Dropping the response channel should lead to fetching the second collation.
|
||||
assert_fetch_collation_request(
|
||||
&mut virtual_overseer,
|
||||
second,
|
||||
test_state.chain_ids[0],
|
||||
).await;
|
||||
assert_fetch_collation_request(&mut virtual_overseer, second, test_state.chain_ids[0])
|
||||
.await;
|
||||
|
||||
assert_matches!(
|
||||
overseer_recv(&mut virtual_overseer).await,
|
||||
@@ -643,20 +633,16 @@ fn fetch_collations_works() {
|
||||
}
|
||||
);
|
||||
|
||||
let response_channel_non_exclusive = assert_fetch_collation_request(
|
||||
&mut virtual_overseer,
|
||||
second,
|
||||
test_state.chain_ids[0],
|
||||
).await;
|
||||
let response_channel_non_exclusive =
|
||||
assert_fetch_collation_request(&mut virtual_overseer, second, test_state.chain_ids[0])
|
||||
.await;
|
||||
|
||||
// Third collator should receive response after that timeout:
|
||||
Delay::new(MAX_UNSHARED_DOWNLOAD_TIME + Duration::from_millis(50)).await;
|
||||
|
||||
let response_channel = assert_fetch_collation_request(
|
||||
&mut virtual_overseer,
|
||||
second,
|
||||
test_state.chain_ids[0],
|
||||
).await;
|
||||
let response_channel =
|
||||
assert_fetch_collation_request(&mut virtual_overseer, second, test_state.chain_ids[0])
|
||||
.await;
|
||||
|
||||
let pov = PoV { block_data: BlockData(vec![1]) };
|
||||
let mut candidate_a = CandidateReceipt::default();
|
||||
@@ -664,26 +650,25 @@ fn fetch_collations_works() {
|
||||
candidate_a.descriptor.relay_parent = second;
|
||||
|
||||
// First request finishes now:
|
||||
response_channel_non_exclusive.send(Ok(
|
||||
CollationFetchingResponse::Collation(
|
||||
candidate_a.clone(),
|
||||
pov.clone(),
|
||||
).encode()
|
||||
)).expect("Sending response should succeed");
|
||||
response_channel_non_exclusive
|
||||
.send(Ok(
|
||||
CollationFetchingResponse::Collation(candidate_a.clone(), pov.clone()).encode()
|
||||
))
|
||||
.expect("Sending response should succeed");
|
||||
|
||||
response_channel.send(Ok(
|
||||
CollationFetchingResponse::Collation(
|
||||
candidate_a.clone(),
|
||||
pov.clone(),
|
||||
).encode()
|
||||
)).expect("Sending response should succeed");
|
||||
response_channel
|
||||
.send(Ok(
|
||||
CollationFetchingResponse::Collation(candidate_a.clone(), pov.clone()).encode()
|
||||
))
|
||||
.expect("Sending response should succeed");
|
||||
|
||||
assert_candidate_backing_second(
|
||||
&mut virtual_overseer,
|
||||
second,
|
||||
test_state.chain_ids[0],
|
||||
&pov,
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
virtual_overseer
|
||||
});
|
||||
@@ -695,18 +680,17 @@ fn fetch_next_collation_on_invalid_collation() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
test_harness(|test_harness| async move {
|
||||
let TestHarness {
|
||||
mut virtual_overseer,
|
||||
} = test_harness;
|
||||
let TestHarness { mut virtual_overseer } = test_harness;
|
||||
|
||||
let second = Hash::random();
|
||||
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::OurViewChange(our_view![test_state.relay_parent, second])
|
||||
),
|
||||
).await;
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::OurViewChange(
|
||||
our_view![test_state.relay_parent, second],
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
|
||||
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
|
||||
@@ -719,14 +703,16 @@ fn fetch_next_collation_on_invalid_collation() {
|
||||
peer_b.clone(),
|
||||
test_state.collators[0].clone(),
|
||||
test_state.chain_ids[0].clone(),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
connect_and_declare_collator(
|
||||
&mut virtual_overseer,
|
||||
peer_c.clone(),
|
||||
test_state.collators[1].clone(),
|
||||
test_state.chain_ids[0].clone(),
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
advertise_collation(&mut virtual_overseer, peer_b.clone(), test_state.relay_parent).await;
|
||||
advertise_collation(&mut virtual_overseer, peer_c.clone(), test_state.relay_parent).await;
|
||||
@@ -735,28 +721,33 @@ fn fetch_next_collation_on_invalid_collation() {
|
||||
&mut virtual_overseer,
|
||||
test_state.relay_parent,
|
||||
test_state.chain_ids[0],
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
let pov = PoV { block_data: BlockData(vec![]) };
|
||||
let mut candidate_a = CandidateReceipt::default();
|
||||
candidate_a.descriptor.para_id = test_state.chain_ids[0];
|
||||
candidate_a.descriptor.relay_parent = test_state.relay_parent;
|
||||
response_channel.send(Ok(
|
||||
CollationFetchingResponse::Collation(
|
||||
candidate_a.clone(),
|
||||
pov.clone(),
|
||||
).encode()
|
||||
)).expect("Sending response should succeed");
|
||||
response_channel
|
||||
.send(Ok(
|
||||
CollationFetchingResponse::Collation(candidate_a.clone(), pov.clone()).encode()
|
||||
))
|
||||
.expect("Sending response should succeed");
|
||||
|
||||
let receipt = assert_candidate_backing_second(
|
||||
&mut virtual_overseer,
|
||||
test_state.relay_parent,
|
||||
test_state.chain_ids[0],
|
||||
&pov,
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
// Inform that the candidate was invalid.
|
||||
overseer_send(&mut virtual_overseer, CollatorProtocolMessage::Invalid(test_state.relay_parent, receipt)).await;
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::Invalid(test_state.relay_parent, receipt),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_matches!(
|
||||
overseer_recv(&mut virtual_overseer).await,
|
||||
@@ -774,7 +765,8 @@ fn fetch_next_collation_on_invalid_collation() {
|
||||
&mut virtual_overseer,
|
||||
test_state.relay_parent,
|
||||
test_state.chain_ids[0],
|
||||
).await;
|
||||
)
|
||||
.await;
|
||||
|
||||
virtual_overseer
|
||||
});
|
||||
@@ -785,9 +777,7 @@ fn inactive_disconnected() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
test_harness(|test_harness| async move {
|
||||
let TestHarness {
|
||||
mut virtual_overseer,
|
||||
} = test_harness;
|
||||
let TestHarness { mut virtual_overseer } = test_harness;
|
||||
|
||||
let pair = CollatorPair::generate().0;
|
||||
|
||||
@@ -795,19 +785,31 @@ fn inactive_disconnected() {
|
||||
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::OurViewChange(our_view![hash_a])
|
||||
)
|
||||
).await;
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::OurViewChange(
|
||||
our_view![hash_a],
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
|
||||
|
||||
let peer_b = PeerId::random();
|
||||
|
||||
connect_and_declare_collator(&mut virtual_overseer, peer_b.clone(), pair.clone(), test_state.chain_ids[0]).await;
|
||||
connect_and_declare_collator(
|
||||
&mut virtual_overseer,
|
||||
peer_b.clone(),
|
||||
pair.clone(),
|
||||
test_state.chain_ids[0],
|
||||
)
|
||||
.await;
|
||||
advertise_collation(&mut virtual_overseer, peer_b.clone(), test_state.relay_parent).await;
|
||||
|
||||
assert_fetch_collation_request(&mut virtual_overseer, test_state.relay_parent, test_state.chain_ids[0]).await;
|
||||
assert_fetch_collation_request(
|
||||
&mut virtual_overseer,
|
||||
test_state.relay_parent,
|
||||
test_state.chain_ids[0],
|
||||
)
|
||||
.await;
|
||||
|
||||
Delay::new(ACTIVITY_TIMEOUT * 3).await;
|
||||
|
||||
@@ -832,9 +834,7 @@ fn activity_extends_life() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
test_harness(|test_harness| async move {
|
||||
let TestHarness {
|
||||
mut virtual_overseer,
|
||||
} = test_harness;
|
||||
let TestHarness { mut virtual_overseer } = test_harness;
|
||||
|
||||
let pair = CollatorPair::generate().0;
|
||||
|
||||
@@ -844,10 +844,11 @@ fn activity_extends_life() {
|
||||
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::OurViewChange(our_view![hash_a, hash_b, hash_c])
|
||||
)
|
||||
).await;
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::OurViewChange(
|
||||
our_view![hash_a, hash_b, hash_c],
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
// 3 heads, 3 times.
|
||||
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
|
||||
@@ -856,13 +857,20 @@ fn activity_extends_life() {
|
||||
|
||||
let peer_b = PeerId::random();
|
||||
|
||||
connect_and_declare_collator(&mut virtual_overseer, peer_b.clone(), pair.clone(), test_state.chain_ids[0]).await;
|
||||
connect_and_declare_collator(
|
||||
&mut virtual_overseer,
|
||||
peer_b.clone(),
|
||||
pair.clone(),
|
||||
test_state.chain_ids[0],
|
||||
)
|
||||
.await;
|
||||
|
||||
Delay::new(ACTIVITY_TIMEOUT * 2 / 3).await;
|
||||
|
||||
advertise_collation(&mut virtual_overseer, peer_b.clone(), hash_a).await;
|
||||
|
||||
assert_fetch_collation_request(&mut virtual_overseer, hash_a, test_state.chain_ids[0]).await;
|
||||
assert_fetch_collation_request(&mut virtual_overseer, hash_a, test_state.chain_ids[0])
|
||||
.await;
|
||||
|
||||
Delay::new(ACTIVITY_TIMEOUT * 2 / 3).await;
|
||||
|
||||
@@ -879,7 +887,8 @@ fn activity_extends_life() {
|
||||
}
|
||||
);
|
||||
|
||||
assert_fetch_collation_request(&mut virtual_overseer, hash_b, test_state.chain_ids[0]).await;
|
||||
assert_fetch_collation_request(&mut virtual_overseer, hash_b, test_state.chain_ids[0])
|
||||
.await;
|
||||
|
||||
Delay::new(ACTIVITY_TIMEOUT * 2 / 3).await;
|
||||
|
||||
@@ -896,7 +905,8 @@ fn activity_extends_life() {
|
||||
}
|
||||
);
|
||||
|
||||
assert_fetch_collation_request(&mut virtual_overseer, hash_c, test_state.chain_ids[0]).await;
|
||||
assert_fetch_collation_request(&mut virtual_overseer, hash_c, test_state.chain_ids[0])
|
||||
.await;
|
||||
|
||||
Delay::new(ACTIVITY_TIMEOUT * 3 / 2).await;
|
||||
|
||||
@@ -922,16 +932,15 @@ fn disconnect_if_no_declare() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
test_harness(|test_harness| async move {
|
||||
let TestHarness {
|
||||
mut virtual_overseer,
|
||||
} = test_harness;
|
||||
let TestHarness { mut virtual_overseer } = test_harness;
|
||||
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::OurViewChange(our_view![test_state.relay_parent])
|
||||
)
|
||||
).await;
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::OurViewChange(
|
||||
our_view![test_state.relay_parent],
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
|
||||
|
||||
@@ -939,14 +948,13 @@ fn disconnect_if_no_declare() {
|
||||
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::PeerConnected(
|
||||
peer_b.clone(),
|
||||
ObservedRole::Full,
|
||||
None,
|
||||
)
|
||||
)
|
||||
).await;
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerConnected(
|
||||
peer_b.clone(),
|
||||
ObservedRole::Full,
|
||||
None,
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_collator_disconnect(&mut virtual_overseer, peer_b.clone()).await;
|
||||
|
||||
@@ -959,18 +967,17 @@ fn disconnect_if_wrong_declare() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
test_harness(|test_harness| async move {
|
||||
let TestHarness {
|
||||
mut virtual_overseer,
|
||||
} = test_harness;
|
||||
let TestHarness { mut virtual_overseer } = test_harness;
|
||||
|
||||
let pair = CollatorPair::generate().0;
|
||||
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::OurViewChange(our_view![test_state.relay_parent])
|
||||
)
|
||||
).await;
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::OurViewChange(
|
||||
our_view![test_state.relay_parent],
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
|
||||
|
||||
@@ -978,28 +985,26 @@ fn disconnect_if_wrong_declare() {
|
||||
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::PeerConnected(
|
||||
peer_b.clone(),
|
||||
ObservedRole::Full,
|
||||
None,
|
||||
)
|
||||
)
|
||||
).await;
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerConnected(
|
||||
peer_b.clone(),
|
||||
ObservedRole::Full,
|
||||
None,
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer_b.clone(),
|
||||
protocol_v1::CollatorProtocolMessage::Declare(
|
||||
pair.public(),
|
||||
ParaId::from(69),
|
||||
pair.sign(&protocol_v1::declare_signature_payload(&peer_b)),
|
||||
)
|
||||
)
|
||||
)
|
||||
).await;
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::PeerMessage(
|
||||
peer_b.clone(),
|
||||
protocol_v1::CollatorProtocolMessage::Declare(
|
||||
pair.public(),
|
||||
ParaId::from(69),
|
||||
pair.sign(&protocol_v1::declare_signature_payload(&peer_b)),
|
||||
),
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_matches!(
|
||||
overseer_recv(&mut virtual_overseer).await,
|
||||
@@ -1023,33 +1028,39 @@ fn view_change_clears_old_collators() {
|
||||
let mut test_state = TestState::default();
|
||||
|
||||
test_harness(|test_harness| async move {
|
||||
let TestHarness {
|
||||
mut virtual_overseer,
|
||||
} = test_harness;
|
||||
let TestHarness { mut virtual_overseer } = test_harness;
|
||||
|
||||
let pair = CollatorPair::generate().0;
|
||||
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::OurViewChange(our_view![test_state.relay_parent])
|
||||
)
|
||||
).await;
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::OurViewChange(
|
||||
our_view![test_state.relay_parent],
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
|
||||
|
||||
let peer_b = PeerId::random();
|
||||
|
||||
connect_and_declare_collator(&mut virtual_overseer, peer_b.clone(), pair.clone(), test_state.chain_ids[0]).await;
|
||||
connect_and_declare_collator(
|
||||
&mut virtual_overseer,
|
||||
peer_b.clone(),
|
||||
pair.clone(),
|
||||
test_state.chain_ids[0],
|
||||
)
|
||||
.await;
|
||||
|
||||
let hash_b = Hash::repeat_byte(69);
|
||||
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(
|
||||
NetworkBridgeEvent::OurViewChange(our_view![hash_b])
|
||||
)
|
||||
).await;
|
||||
CollatorProtocolMessage::NetworkBridgeUpdateV1(NetworkBridgeEvent::OurViewChange(
|
||||
our_view![hash_b],
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
test_state.group_rotation_info = test_state.group_rotation_info.bump_rotation();
|
||||
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
|
||||
|
||||
@@ -19,11 +19,10 @@
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use polkadot_node_subsystem_util::{Fault, runtime, unwrap_non_fatal};
|
||||
use polkadot_node_subsystem_util::{runtime, unwrap_non_fatal, Fault};
|
||||
use polkadot_subsystem::SubsystemError;
|
||||
|
||||
use crate::LOG_TARGET;
|
||||
use crate::sender;
|
||||
use crate::{sender, LOG_TARGET};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error(transparent)]
|
||||
@@ -53,7 +52,6 @@ impl From<sender::Error> for Error {
|
||||
/// Fatal errors of this subsystem.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Fatal {
|
||||
|
||||
/// Receiving subsystem message from overseer failed.
|
||||
#[error("Receiving message from overseer failed")]
|
||||
SubsystemReceive(#[source] SubsystemError),
|
||||
@@ -91,9 +89,7 @@ pub type FatalResult<T> = std::result::Result<T, Fatal>;
|
||||
///
|
||||
/// 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)
|
||||
-> std::result::Result<(), Fatal>
|
||||
{
|
||||
pub fn log_error(result: Result<()>, ctx: &'static str) -> std::result::Result<(), Fatal> {
|
||||
if let Some(error) = unwrap_non_fatal(result.map_err(|e| e.0))? {
|
||||
tracing::warn!(target: LOG_TARGET, error = ?error, ctx);
|
||||
}
|
||||
|
||||
@@ -24,21 +24,17 @@
|
||||
//! The sender is responsible for getting our vote out, see [`sender`]. The receiver handles
|
||||
//! incoming [`DisputeRequest`]s and offers spam protection, see [`receiver`].
|
||||
|
||||
use futures::channel::{mpsc};
|
||||
use futures::{FutureExt, StreamExt, TryFutureExt};
|
||||
use futures::{channel::mpsc, FutureExt, StreamExt, TryFutureExt};
|
||||
|
||||
use polkadot_node_network_protocol::authority_discovery::AuthorityDiscovery;
|
||||
use sp_keystore::SyncCryptoStorePtr;
|
||||
|
||||
use polkadot_node_primitives::DISPUTE_WINDOW;
|
||||
use polkadot_node_subsystem_util::{runtime, runtime::RuntimeInfo};
|
||||
use polkadot_subsystem::{
|
||||
overseer, messages::DisputeDistributionMessage, FromOverseer, OverseerSignal, SpawnedSubsystem,
|
||||
messages::DisputeDistributionMessage, overseer, FromOverseer, OverseerSignal, SpawnedSubsystem,
|
||||
SubsystemContext, SubsystemError,
|
||||
};
|
||||
use polkadot_node_subsystem_util::{
|
||||
runtime,
|
||||
runtime::RuntimeInfo,
|
||||
};
|
||||
|
||||
/// ## The sender [`DisputeSender`]
|
||||
///
|
||||
@@ -85,8 +81,7 @@ use self::receiver::DisputesReceiver;
|
||||
|
||||
/// Error and [`Result`] type for this subsystem.
|
||||
mod error;
|
||||
use error::{Fatal, FatalResult};
|
||||
use error::{Result, log_error};
|
||||
use error::{log_error, Fatal, FatalResult, Result};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -119,7 +114,8 @@ impl<Context, AD> overseer::Subsystem<Context, SubsystemError> for DisputeDistri
|
||||
where
|
||||
Context: SubsystemContext<Message = DisputeDistributionMessage>
|
||||
+ overseer::SubsystemContext<Message = DisputeDistributionMessage>
|
||||
+ Sync + Send,
|
||||
+ Sync
|
||||
+ Send,
|
||||
AD: AuthorityDiscovery + Clone,
|
||||
{
|
||||
fn start(self, ctx: Context) -> SpawnedSubsystem {
|
||||
@@ -128,10 +124,7 @@ where
|
||||
.map_err(|e| SubsystemError::with_origin("dispute-distribution", e))
|
||||
.boxed();
|
||||
|
||||
SpawnedSubsystem {
|
||||
name: "dispute-distribution-subsystem",
|
||||
future,
|
||||
}
|
||||
SpawnedSubsystem { name: "dispute-distribution-subsystem", future }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +148,8 @@ where
|
||||
where
|
||||
Context: SubsystemContext<Message = DisputeDistributionMessage>
|
||||
+ overseer::SubsystemContext<Message = DisputeDistributionMessage>
|
||||
+ Sync + Send,
|
||||
+ Sync
|
||||
+ Send,
|
||||
{
|
||||
loop {
|
||||
let message = MuxedMessage::receive(&mut ctx, &mut self.sender_rx).await;
|
||||
@@ -168,52 +162,43 @@ where
|
||||
Ok(SignalResult::Continue) => Ok(()),
|
||||
Err(f) => Err(f),
|
||||
}
|
||||
}
|
||||
},
|
||||
FromOverseer::Communication { msg } =>
|
||||
self.handle_subsystem_message(&mut ctx, msg).await,
|
||||
};
|
||||
log_error(result, "on FromOverseer")?;
|
||||
}
|
||||
},
|
||||
MuxedMessage::Sender(result) => {
|
||||
self.disputes_sender.on_task_message(
|
||||
result.ok_or(Fatal::SenderExhausted)?
|
||||
)
|
||||
.await;
|
||||
}
|
||||
self.disputes_sender
|
||||
.on_task_message(result.ok_or(Fatal::SenderExhausted)?)
|
||||
.await;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle overseer signals.
|
||||
async fn handle_signals<Context: SubsystemContext> (
|
||||
async fn handle_signals<Context: SubsystemContext>(
|
||||
&mut self,
|
||||
ctx: &mut Context,
|
||||
signal: OverseerSignal,
|
||||
) -> Result<SignalResult>
|
||||
{
|
||||
) -> Result<SignalResult> {
|
||||
match signal {
|
||||
OverseerSignal::Conclude =>
|
||||
return Ok(SignalResult::Conclude),
|
||||
OverseerSignal::Conclude => return Ok(SignalResult::Conclude),
|
||||
OverseerSignal::ActiveLeaves(update) => {
|
||||
self.disputes_sender.update_leaves(
|
||||
ctx,
|
||||
&mut self.runtime,
|
||||
update
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
OverseerSignal::BlockFinalized(_,_) => {}
|
||||
self.disputes_sender.update_leaves(ctx, &mut self.runtime, update).await?;
|
||||
},
|
||||
OverseerSignal::BlockFinalized(_, _) => {},
|
||||
};
|
||||
Ok(SignalResult::Continue)
|
||||
}
|
||||
|
||||
/// Handle `DisputeDistributionMessage`s.
|
||||
async fn handle_subsystem_message<Context: SubsystemContext> (
|
||||
async fn handle_subsystem_message<Context: SubsystemContext>(
|
||||
&mut self,
|
||||
ctx: &mut Context,
|
||||
msg: DisputeDistributionMessage
|
||||
) -> Result<()>
|
||||
{
|
||||
msg: DisputeDistributionMessage,
|
||||
) -> Result<()> {
|
||||
match msg {
|
||||
DisputeDistributionMessage::SendDispute(dispute_msg) =>
|
||||
self.disputes_sender.start_sender(ctx, &mut self.runtime, dispute_msg).await?,
|
||||
@@ -223,14 +208,12 @@ where
|
||||
ctx.sender().clone(),
|
||||
receiver,
|
||||
self.authority_discovery.clone(),
|
||||
self.metrics.clone()
|
||||
self.metrics.clone(),
|
||||
);
|
||||
|
||||
ctx
|
||||
.spawn("disputes-receiver", receiver.run().boxed(),)
|
||||
ctx.spawn("disputes-receiver", receiver.run().boxed())
|
||||
.map_err(Fatal::SpawnTask)?;
|
||||
},
|
||||
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -247,7 +230,8 @@ enum MuxedMessage {
|
||||
|
||||
impl MuxedMessage {
|
||||
async fn receive(
|
||||
ctx: &mut (impl SubsystemContext<Message = DisputeDistributionMessage> + overseer::SubsystemContext<Message = DisputeDistributionMessage>),
|
||||
ctx: &mut (impl SubsystemContext<Message = DisputeDistributionMessage>
|
||||
+ overseer::SubsystemContext<Message = DisputeDistributionMessage>),
|
||||
from_sender: &mut mpsc::Receiver<TaskFinish>,
|
||||
) -> Self {
|
||||
// We are only fusing here to make `select` happy, in reality we will quit if the stream
|
||||
|
||||
@@ -14,9 +14,13 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use polkadot_node_subsystem_util::metrics::prometheus::{Counter, U64, Registry, PrometheusError, CounterVec, Opts};
|
||||
use polkadot_node_subsystem_util::metrics::prometheus;
|
||||
use polkadot_node_subsystem_util::metrics;
|
||||
use polkadot_node_subsystem_util::{
|
||||
metrics,
|
||||
metrics::{
|
||||
prometheus,
|
||||
prometheus::{Counter, CounterVec, Opts, PrometheusError, Registry, U64},
|
||||
},
|
||||
};
|
||||
|
||||
/// Label for success counters.
|
||||
pub const SUCCEEDED: &'static str = "succeeded";
|
||||
@@ -81,7 +85,7 @@ impl metrics::Metrics for Metrics {
|
||||
"parachain_dispute_distribution_sent_requests",
|
||||
"Total number of sent requests.",
|
||||
),
|
||||
&["success"]
|
||||
&["success"],
|
||||
)?,
|
||||
registry,
|
||||
)?,
|
||||
@@ -98,7 +102,7 @@ impl metrics::Metrics for Metrics {
|
||||
"parachain_dispute_distribution_imported_requests",
|
||||
"Total number of imported requests.",
|
||||
),
|
||||
&["success"]
|
||||
&["success"],
|
||||
)?,
|
||||
registry,
|
||||
)?,
|
||||
@@ -106,4 +110,3 @@ impl metrics::Metrics for Metrics {
|
||||
Ok(Metrics(Some(metrics)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,9 +19,8 @@
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use polkadot_node_network_protocol::PeerId;
|
||||
use polkadot_node_network_protocol::request_response::request::ReceiveError;
|
||||
use polkadot_node_subsystem_util::{Fault, runtime, unwrap_non_fatal};
|
||||
use polkadot_node_network_protocol::{request_response::request::ReceiveError, PeerId};
|
||||
use polkadot_node_subsystem_util::{runtime, unwrap_non_fatal, Fault};
|
||||
|
||||
use crate::LOG_TARGET;
|
||||
|
||||
@@ -100,9 +99,7 @@ pub type NonFatalResult<T> = std::result::Result<T, NonFatal>;
|
||||
///
|
||||
/// 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<()>)
|
||||
-> std::result::Result<(), Fatal>
|
||||
{
|
||||
pub fn log_error(result: Result<()>) -> std::result::Result<(), Fatal> {
|
||||
if let Some(error) = unwrap_non_fatal(result.map_err(|e| e.0))? {
|
||||
tracing::warn!(target: LOG_TARGET, error = ?error);
|
||||
}
|
||||
|
||||
@@ -14,47 +14,43 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use futures::FutureExt;
|
||||
use futures::Stream;
|
||||
use futures::future::{BoxFuture, poll_fn};
|
||||
use futures::stream::FusedStream;
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
future::{poll_fn, BoxFuture},
|
||||
stream::{FusedStream, FuturesUnordered, StreamExt},
|
||||
FutureExt, Stream,
|
||||
};
|
||||
use lru::LruCache;
|
||||
use futures::{channel::mpsc, channel::oneshot, stream::StreamExt, stream::FuturesUnordered};
|
||||
|
||||
use polkadot_node_network_protocol::{
|
||||
PeerId,
|
||||
UnifiedReputationChange as Rep,
|
||||
authority_discovery::AuthorityDiscovery,
|
||||
request_response::{
|
||||
request::{OutgoingResponse, OutgoingResponseSender},
|
||||
v1::{DisputeRequest, DisputeResponse},
|
||||
IncomingRequest,
|
||||
request::OutgoingResponse,
|
||||
request::OutgoingResponseSender,
|
||||
v1::DisputeRequest,
|
||||
v1::DisputeResponse,
|
||||
},
|
||||
PeerId, UnifiedReputationChange as Rep,
|
||||
};
|
||||
use polkadot_node_primitives::DISPUTE_WINDOW;
|
||||
use polkadot_node_subsystem_util::{
|
||||
runtime,
|
||||
runtime::RuntimeInfo,
|
||||
};
|
||||
use polkadot_node_subsystem_util::{runtime, runtime::RuntimeInfo};
|
||||
use polkadot_subsystem::{
|
||||
messages::{AllMessages, DisputeCoordinatorMessage, ImportStatementsResult},
|
||||
SubsystemSender,
|
||||
messages::{
|
||||
AllMessages, DisputeCoordinatorMessage, ImportStatementsResult,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::metrics::{FAILED, SUCCEEDED};
|
||||
use crate::{LOG_TARGET, Metrics};
|
||||
use crate::{
|
||||
metrics::{FAILED, SUCCEEDED},
|
||||
Metrics, LOG_TARGET,
|
||||
};
|
||||
|
||||
mod error;
|
||||
use self::error::{log_error, FatalResult, NonFatalResult, NonFatal, Fatal, Result};
|
||||
use self::error::{log_error, Fatal, FatalResult, NonFatal, NonFatalResult, Result};
|
||||
|
||||
const COST_INVALID_REQUEST: Rep = Rep::CostMajor("Received message could not be decoded.");
|
||||
const COST_INVALID_SIGNATURE: Rep = Rep::Malicious("Signatures were invalid.");
|
||||
@@ -129,12 +125,13 @@ impl MuxedMessage {
|
||||
return Poll::Ready(Ok(MuxedMessage::ConfirmedImport(v)))
|
||||
}
|
||||
Poll::Pending
|
||||
}).await
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl<Sender: SubsystemSender, AD> DisputesReceiver<Sender, AD>
|
||||
where
|
||||
where
|
||||
AD: AuthorityDiscovery,
|
||||
{
|
||||
/// Create a new receiver which can be `run`.
|
||||
@@ -167,41 +164,32 @@ where
|
||||
pub async fn run(mut self) {
|
||||
loop {
|
||||
match log_error(self.run_inner().await) {
|
||||
Ok(()) => {}
|
||||
Ok(()) => {},
|
||||
Err(Fatal::RequestChannelFinished) => {
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Incoming request stream exhausted - shutting down?"
|
||||
);
|
||||
return
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
?err,
|
||||
"Dispute receiver died."
|
||||
);
|
||||
},
|
||||
Err(err) => {
|
||||
tracing::warn!(target: LOG_TARGET, ?err, "Dispute receiver died.");
|
||||
return
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Actual work happening here.
|
||||
async fn run_inner(&mut self) -> Result<()> {
|
||||
|
||||
let msg = MuxedMessage::receive(
|
||||
&mut self.pending_imports,
|
||||
&mut self.receiver
|
||||
)
|
||||
.await?;
|
||||
let msg = MuxedMessage::receive(&mut self.pending_imports, &mut self.receiver).await?;
|
||||
|
||||
let raw = match msg {
|
||||
// We need to clean up futures, to make sure responses are sent:
|
||||
MuxedMessage::ConfirmedImport(m_bad) => {
|
||||
self.ban_bad_peer(m_bad)?;
|
||||
return Ok(())
|
||||
}
|
||||
},
|
||||
MuxedMessage::NewRequest(req) => req,
|
||||
};
|
||||
|
||||
@@ -211,23 +199,20 @@ where
|
||||
|
||||
// Only accept messages from validators:
|
||||
if self.authority_discovery.get_authority_id_by_peer_id(raw.peer).await.is_none() {
|
||||
raw.pending_response.send(
|
||||
sc_network::config::OutgoingResponse {
|
||||
raw.pending_response
|
||||
.send(sc_network::config::OutgoingResponse {
|
||||
result: Err(()),
|
||||
reputation_changes: vec![COST_NOT_A_VALIDATOR.into_base_rep()],
|
||||
sent_feedback: None,
|
||||
}
|
||||
)
|
||||
.map_err(|_| NonFatal::SendResponse(peer))?;
|
||||
})
|
||||
.map_err(|_| NonFatal::SendResponse(peer))?;
|
||||
|
||||
return Err(NonFatal::NotAValidator(peer).into())
|
||||
}
|
||||
|
||||
let incoming = IncomingRequest::<DisputeRequest>::try_from_raw(
|
||||
raw,
|
||||
vec![COST_INVALID_REQUEST]
|
||||
)
|
||||
.map_err(NonFatal::FromRawRequest)?;
|
||||
let incoming =
|
||||
IncomingRequest::<DisputeRequest>::try_from_raw(raw, vec![COST_INVALID_REQUEST])
|
||||
.map_err(NonFatal::FromRawRequest)?;
|
||||
|
||||
// Immediately drop requests from peers that already have requests in flight or have
|
||||
// been banned recently (flood protection):
|
||||
@@ -252,54 +237,49 @@ where
|
||||
}
|
||||
|
||||
/// Start importing votes for the given request.
|
||||
async fn start_import(
|
||||
&mut self,
|
||||
incoming: IncomingRequest<DisputeRequest>,
|
||||
) -> Result<()> {
|
||||
async fn start_import(&mut self, incoming: IncomingRequest<DisputeRequest>) -> Result<()> {
|
||||
let IncomingRequest { peer, payload, pending_response } = incoming;
|
||||
|
||||
let IncomingRequest {
|
||||
peer, payload, pending_response,
|
||||
} = incoming;
|
||||
|
||||
let info = self.runtime.get_session_info_by_index(
|
||||
&mut self.sender,
|
||||
payload.0.candidate_receipt.descriptor.relay_parent,
|
||||
payload.0.session_index
|
||||
)
|
||||
.await?;
|
||||
let info = self
|
||||
.runtime
|
||||
.get_session_info_by_index(
|
||||
&mut self.sender,
|
||||
payload.0.candidate_receipt.descriptor.relay_parent,
|
||||
payload.0.session_index,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let votes_result = payload.0.try_into_signed_votes(&info.session_info);
|
||||
|
||||
let (candidate_receipt, valid_vote, invalid_vote) = match votes_result {
|
||||
Err(()) => { // Signature invalid:
|
||||
pending_response.send_outgoing_response(
|
||||
OutgoingResponse {
|
||||
Err(()) => {
|
||||
// Signature invalid:
|
||||
pending_response
|
||||
.send_outgoing_response(OutgoingResponse {
|
||||
result: Err(()),
|
||||
reputation_changes: vec![COST_INVALID_SIGNATURE],
|
||||
sent_feedback: None,
|
||||
}
|
||||
)
|
||||
.map_err(|_| NonFatal::SetPeerReputation(peer))?;
|
||||
})
|
||||
.map_err(|_| NonFatal::SetPeerReputation(peer))?;
|
||||
|
||||
return Err(From::from(NonFatal::InvalidSignature(peer)))
|
||||
}
|
||||
},
|
||||
Ok(votes) => votes,
|
||||
};
|
||||
|
||||
let (pending_confirmation, confirmation_rx) = oneshot::channel();
|
||||
let candidate_hash = candidate_receipt.hash();
|
||||
self.sender.send_message(
|
||||
AllMessages::DisputeCoordinator(
|
||||
self.sender
|
||||
.send_message(AllMessages::DisputeCoordinator(
|
||||
DisputeCoordinatorMessage::ImportStatements {
|
||||
candidate_hash,
|
||||
candidate_receipt,
|
||||
session: valid_vote.0.session_index(),
|
||||
statements: vec![valid_vote, invalid_vote],
|
||||
pending_confirmation,
|
||||
}
|
||||
)
|
||||
)
|
||||
.await;
|
||||
},
|
||||
))
|
||||
.await;
|
||||
|
||||
self.pending_imports.push(peer, confirmation_rx, pending_response);
|
||||
Ok(())
|
||||
@@ -310,16 +290,16 @@ where
|
||||
/// In addition we report import metrics.
|
||||
fn ban_bad_peer(
|
||||
&mut self,
|
||||
result: NonFatalResult<(PeerId, ImportStatementsResult)>
|
||||
result: NonFatalResult<(PeerId, ImportStatementsResult)>,
|
||||
) -> NonFatalResult<()> {
|
||||
match result? {
|
||||
(_, ImportStatementsResult::ValidImport) => {
|
||||
(_, ImportStatementsResult::ValidImport) => {
|
||||
self.metrics.on_imported(SUCCEEDED);
|
||||
}
|
||||
},
|
||||
(bad_peer, ImportStatementsResult::InvalidImport) => {
|
||||
self.metrics.on_imported(FAILED);
|
||||
self.banned_peers.put(bad_peer, ());
|
||||
}
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -335,24 +315,22 @@ struct PendingImports {
|
||||
|
||||
impl PendingImports {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
futures: FuturesUnordered::new(),
|
||||
peers: HashSet::new(),
|
||||
}
|
||||
Self { futures: FuturesUnordered::new(), peers: HashSet::new() }
|
||||
}
|
||||
|
||||
pub fn push(
|
||||
&mut self,
|
||||
peer: PeerId,
|
||||
handled: oneshot::Receiver<ImportStatementsResult>,
|
||||
pending_response: OutgoingResponseSender<DisputeRequest>
|
||||
pending_response: OutgoingResponseSender<DisputeRequest>,
|
||||
) {
|
||||
self.peers.insert(peer);
|
||||
self.futures.push(
|
||||
async move {
|
||||
let r = respond_to_request(peer, handled, pending_response).await;
|
||||
(peer, r)
|
||||
}.boxed()
|
||||
}
|
||||
.boxed(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -369,23 +347,19 @@ impl PendingImports {
|
||||
|
||||
impl Stream for PendingImports {
|
||||
type Item = NonFatalResult<(PeerId, ImportStatementsResult)>;
|
||||
fn poll_next(
|
||||
mut self: Pin<&mut Self>,
|
||||
ctx: &mut Context<'_>
|
||||
) -> Poll<Option<Self::Item>> {
|
||||
fn poll_next(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
match Pin::new(&mut self.futures).poll_next(ctx) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(None) => Poll::Ready(None),
|
||||
Poll::Ready(Some((peer, result))) => {
|
||||
self.peers.remove(&peer);
|
||||
Poll::Ready(Some(result.map(|r| (peer,r))))
|
||||
}
|
||||
Poll::Ready(Some(result.map(|r| (peer, r))))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
impl FusedStream for PendingImports {
|
||||
fn is_terminated(&self) -> bool {
|
||||
fn is_terminated(&self) -> bool {
|
||||
self.futures.is_terminated()
|
||||
}
|
||||
}
|
||||
@@ -398,27 +372,21 @@ impl FusedStream for PendingImports {
|
||||
async fn respond_to_request(
|
||||
peer: PeerId,
|
||||
handled: oneshot::Receiver<ImportStatementsResult>,
|
||||
pending_response: OutgoingResponseSender<DisputeRequest>
|
||||
pending_response: OutgoingResponseSender<DisputeRequest>,
|
||||
) -> NonFatalResult<ImportStatementsResult> {
|
||||
|
||||
let result = handled
|
||||
.await
|
||||
.map_err(|_| NonFatal::ImportCanceled(peer))?
|
||||
;
|
||||
let result = handled.await.map_err(|_| NonFatal::ImportCanceled(peer))?;
|
||||
|
||||
let response = match result {
|
||||
ImportStatementsResult::ValidImport =>
|
||||
OutgoingResponse {
|
||||
result: Ok(DisputeResponse::Confirmed),
|
||||
reputation_changes: Vec::new(),
|
||||
sent_feedback: None,
|
||||
},
|
||||
ImportStatementsResult::InvalidImport =>
|
||||
OutgoingResponse {
|
||||
result: Err(()),
|
||||
reputation_changes: vec![COST_INVALID_CANDIDATE],
|
||||
sent_feedback: None,
|
||||
},
|
||||
ImportStatementsResult::ValidImport => OutgoingResponse {
|
||||
result: Ok(DisputeResponse::Confirmed),
|
||||
reputation_changes: Vec::new(),
|
||||
sent_feedback: None,
|
||||
},
|
||||
ImportStatementsResult::InvalidImport => OutgoingResponse {
|
||||
result: Err(()),
|
||||
reputation_changes: vec![COST_INVALID_CANDIDATE],
|
||||
sent_feedback: None,
|
||||
},
|
||||
};
|
||||
|
||||
pending_response
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user