Introduce interface for relay chain interaction (#835)

This commit is contained in:
Sebastian Kunert
2021-12-22 19:02:11 +01:00
committed by GitHub
parent 7acfd4f80a
commit 876e594f59
30 changed files with 1512 additions and 1607 deletions
+37 -153
View File
@@ -20,12 +20,8 @@
//! that use the relay chain provided consensus. See [`BlockAnnounceValidator`]
//! and [`WaitToAnnounce`] for more information about this implementation.
use sc_client_api::{Backend, BlockchainEvents};
use sp_api::ProvideRuntimeApi;
use sp_blockchain::HeaderBackend;
use sp_consensus::{
block_validation::{BlockAnnounceValidator as BlockAnnounceValidatorT, Validation},
SyncOracle,
use sp_consensus::block_validation::{
BlockAnnounceValidator as BlockAnnounceValidatorT, Validation,
};
use sp_core::traits::SpawnNamed;
use sp_runtime::{
@@ -33,12 +29,12 @@ use sp_runtime::{
traits::{Block as BlockT, Header as HeaderT},
};
use polkadot_client::ClientHandle;
use cumulus_relay_chain_interface::RelayChainInterface;
use polkadot_node_primitives::{CollationSecondedSignal, Statement};
use polkadot_parachain::primitives::HeadData;
use polkadot_primitives::v1::{
Block as PBlock, CandidateReceipt, CompactStatement, Hash as PHash, Id as ParaId,
OccupiedCoreAssumption, ParachainHost, SigningContext, UncheckedSigned,
OccupiedCoreAssumption, SigningContext, UncheckedSigned,
};
use codec::{Decode, DecodeAll, Encode};
@@ -50,11 +46,8 @@ use futures::{
use std::{convert::TryFrom, fmt, marker::PhantomData, pin::Pin, sync::Arc};
use wait_on_relay_chain_block::WaitOnRelayChainBlock;
#[cfg(test)]
mod tests;
mod wait_on_relay_chain_block;
const LOG_TARGET: &str = "sync::cumulus";
@@ -135,19 +128,18 @@ impl BlockAnnounceData {
/// Check the signature of the statement.
///
/// Returns an `Err(_)` if it failed.
fn check_signature<P>(
fn check_signature<RCInterface>(
self,
relay_chain_client: &Arc<P>,
relay_chain_client: &RCInterface,
) -> Result<Validation, BlockAnnounceError>
where
P: ProvideRuntimeApi<PBlock> + Send + Sync + 'static,
P::Api: ParachainHost<PBlock>,
RCInterface: RelayChainInterface + 'static,
{
let runtime_api = relay_chain_client.runtime_api();
let validator_index = self.statement.unchecked_validator_index();
let runtime_api_block_id = BlockId::Hash(self.relay_parent);
let session_index = match runtime_api.session_index_for_child(&runtime_api_block_id) {
let session_index = match relay_chain_client.session_index_for_child(&runtime_api_block_id)
{
Ok(r) => r,
Err(e) => return Err(BlockAnnounceError(format!("{:?}", e))),
};
@@ -155,7 +147,7 @@ impl BlockAnnounceData {
let signing_context = SigningContext { parent_hash: self.relay_parent, session_index };
// Check that the signer is a legit validator.
let authorities = match runtime_api.validators(&runtime_api_block_id) {
let authorities = match relay_chain_client.validators(&runtime_api_block_id) {
Ok(r) => r,
Err(e) => return Err(BlockAnnounceError(format!("{:?}", e))),
};
@@ -230,52 +222,37 @@ impl TryFrom<&'_ CollationSecondedSignal> for BlockAnnounceData {
/// chain. If it is at the tip, it is required to provide a justification or otherwise we reject
/// it. However, if the announcement is for a block below the tip the announcement is accepted
/// as it probably comes from a node that is currently syncing the chain.
pub struct BlockAnnounceValidator<Block, R, B, BCE> {
pub struct BlockAnnounceValidator<Block, RCInterface> {
phantom: PhantomData<Block>,
relay_chain_client: Arc<R>,
relay_chain_backend: Arc<B>,
relay_chain_interface: RCInterface,
para_id: ParaId,
relay_chain_sync_oracle: Box<dyn SyncOracle + Send>,
wait_on_relay_chain_block: WaitOnRelayChainBlock<B, BCE>,
}
impl<Block, R, B, BCE> BlockAnnounceValidator<Block, R, B, BCE> {
impl<Block, RCInterface> BlockAnnounceValidator<Block, RCInterface>
where
RCInterface: Clone,
{
/// Create a new [`BlockAnnounceValidator`].
pub fn new(
relay_chain_client: Arc<R>,
para_id: ParaId,
relay_chain_sync_oracle: Box<dyn SyncOracle + Send>,
relay_chain_backend: Arc<B>,
relay_chain_blockchain_events: Arc<BCE>,
) -> Self {
pub fn new(relay_chain_interface: RCInterface, para_id: ParaId) -> Self {
Self {
phantom: Default::default(),
relay_chain_client,
relay_chain_interface: relay_chain_interface.clone(),
para_id,
relay_chain_sync_oracle,
relay_chain_backend: relay_chain_backend.clone(),
wait_on_relay_chain_block: WaitOnRelayChainBlock::new(
relay_chain_backend,
relay_chain_blockchain_events,
),
}
}
}
impl<Block: BlockT, R, B, BCE> BlockAnnounceValidator<Block, R, B, BCE>
impl<Block: BlockT, RCInterface> BlockAnnounceValidator<Block, RCInterface>
where
R: ProvideRuntimeApi<PBlock> + Send + Sync + 'static,
R::Api: ParachainHost<PBlock>,
B: Backend<PBlock> + 'static,
RCInterface: RelayChainInterface + Clone,
{
/// Get the included block of the given parachain in the relay chain.
fn included_block(
relay_chain_client: &R,
relay_chain_interface: &RCInterface,
block_id: &BlockId<PBlock>,
para_id: ParaId,
) -> Result<Block::Header, BoxedError> {
let validation_data = relay_chain_client
.runtime_api()
let validation_data = relay_chain_interface
.persisted_validation_data(block_id, para_id, OccupiedCoreAssumption::TimedOut)
.map_err(|e| Box::new(BlockAnnounceError(format!("{:?}", e))) as Box<_>)?
.ok_or_else(|| {
@@ -293,12 +270,11 @@ where
/// Get the backed block hash of the given parachain in the relay chain.
fn backed_block_hash(
relay_chain_client: &R,
relay_chain_interface: &RCInterface,
block_id: &BlockId<PBlock>,
para_id: ParaId,
) -> Result<Option<PHash>, BoxedError> {
let candidate_receipt = relay_chain_client
.runtime_api()
let candidate_receipt = relay_chain_interface
.candidate_pending_availability(block_id, para_id)
.map_err(|e| Box::new(BlockAnnounceError(format!("{:?}", e))) as Box<_>)?;
@@ -310,21 +286,20 @@ where
&self,
header: Block::Header,
) -> impl Future<Output = Result<Validation, BoxedError>> {
let relay_chain_client = self.relay_chain_client.clone();
let relay_chain_backend = self.relay_chain_backend.clone();
let relay_chain_interface = self.relay_chain_interface.clone();
let para_id = self.para_id;
async move {
// Check if block is equal or higher than best (this requires a justification)
let relay_chain_info = relay_chain_backend.blockchain().info();
let runtime_api_block_id = BlockId::Hash(relay_chain_info.best_hash);
let relay_chain_best_hash = relay_chain_interface.best_block_hash();
let runtime_api_block_id = BlockId::Hash(relay_chain_best_hash);
let block_number = header.number();
let best_head =
Self::included_block(&*relay_chain_client, &runtime_api_block_id, para_id)?;
Self::included_block(&relay_chain_interface, &runtime_api_block_id, para_id)?;
let known_best_number = best_head.number();
let backed_block =
|| Self::backed_block_hash(&*relay_chain_client, &runtime_api_block_id, para_id);
|| Self::backed_block_hash(&relay_chain_interface, &runtime_api_block_id, para_id);
if best_head == header {
tracing::debug!(target: LOG_TARGET, "Announced block matches best block.",);
@@ -348,20 +323,17 @@ where
}
}
impl<Block: BlockT, P, B, BCE> BlockAnnounceValidatorT<Block>
for BlockAnnounceValidator<Block, P, B, BCE>
impl<Block: BlockT, RCInterface> BlockAnnounceValidatorT<Block>
for BlockAnnounceValidator<Block, RCInterface>
where
P: ProvideRuntimeApi<PBlock> + Send + Sync + 'static,
P::Api: ParachainHost<PBlock>,
B: Backend<PBlock> + 'static,
BCE: BlockchainEvents<PBlock> + 'static + Send + Sync,
RCInterface: RelayChainInterface + Clone + 'static,
{
fn validate(
&mut self,
header: &Block::Header,
mut data: &[u8],
) -> Pin<Box<dyn Future<Output = Result<Validation, BoxedError>> + Send>> {
if self.relay_chain_sync_oracle.is_major_syncing() {
if self.relay_chain_interface.is_major_syncing() {
return ready(Ok(Validation::Success { is_new_best: false })).boxed()
}
@@ -381,9 +353,8 @@ where
.boxed(),
};
let relay_chain_client = self.relay_chain_client.clone();
let relay_chain_interface = self.relay_chain_interface.clone();
let header_encoded = header.encode();
let wait_on_relay_chain_block = self.wait_on_relay_chain_block.clone();
async move {
if let Err(e) = block_announce_data.validate(header_encoded) {
@@ -392,106 +363,19 @@ where
let relay_parent = block_announce_data.receipt.descriptor.relay_parent;
wait_on_relay_chain_block
.wait_on_relay_chain_block(relay_parent)
relay_chain_interface
.wait_for_block(relay_parent)
.await
.map_err(|e| Box::new(BlockAnnounceError(e.to_string())) as Box<_>)?;
block_announce_data
.check_signature(&relay_chain_client)
.check_signature(&relay_chain_interface)
.map_err(|e| Box::new(e) as Box<_>)
}
.boxed()
}
}
/// Build a block announce validator instance.
///
/// Returns a boxed [`BlockAnnounceValidator`].
pub fn build_block_announce_validator<Block: BlockT, B>(
relay_chain_client: polkadot_client::Client,
para_id: ParaId,
relay_chain_sync_oracle: Box<dyn SyncOracle + Send>,
relay_chain_backend: Arc<B>,
) -> Box<dyn BlockAnnounceValidatorT<Block> + Send>
where
B: Backend<PBlock> + Send + 'static,
{
BlockAnnounceValidatorBuilder::new(
relay_chain_client,
para_id,
relay_chain_sync_oracle,
relay_chain_backend,
)
.build()
}
/// Block announce validator builder.
///
/// Builds a [`BlockAnnounceValidator`] for a parachain. As this requires
/// a concrete relay chain client instance, the builder takes a [`polkadot_client::Client`]
/// that wraps this concrete instanace. By using [`polkadot_client::ExecuteWithClient`]
/// the builder gets access to this concrete instance.
struct BlockAnnounceValidatorBuilder<Block, B> {
phantom: PhantomData<Block>,
relay_chain_client: polkadot_client::Client,
para_id: ParaId,
relay_chain_sync_oracle: Box<dyn SyncOracle + Send>,
relay_chain_backend: Arc<B>,
}
impl<Block: BlockT, B> BlockAnnounceValidatorBuilder<Block, B>
where
B: Backend<PBlock> + Send + 'static,
{
/// Create a new instance of the builder.
fn new(
relay_chain_client: polkadot_client::Client,
para_id: ParaId,
relay_chain_sync_oracle: Box<dyn SyncOracle + Send>,
relay_chain_backend: Arc<B>,
) -> Self {
Self {
relay_chain_client,
para_id,
relay_chain_sync_oracle,
relay_chain_backend,
phantom: PhantomData,
}
}
/// Build the block announce validator.
fn build(self) -> Box<dyn BlockAnnounceValidatorT<Block> + Send> {
self.relay_chain_client.clone().execute_with(self)
}
}
impl<Block: BlockT, B> polkadot_client::ExecuteWithClient
for BlockAnnounceValidatorBuilder<Block, B>
where
B: Backend<PBlock> + Send + 'static,
{
type Output = Box<dyn BlockAnnounceValidatorT<Block> + Send>;
fn execute_with_client<PClient, Api, PBackend>(self, client: Arc<PClient>) -> Self::Output
where
<Api as sp_api::ApiExt<PBlock>>::StateBackend:
sp_api::StateBackend<sp_runtime::traits::BlakeTwo256>,
PBackend: Backend<PBlock>,
PBackend::State: sp_api::StateBackend<sp_runtime::traits::BlakeTwo256>,
Api: polkadot_client::RuntimeApiCollection<StateBackend = PBackend::State>,
PClient: polkadot_client::AbstractClient<PBlock, PBackend, Api = Api> + 'static,
{
Box::new(BlockAnnounceValidator::new(
client.clone(),
self.para_id,
self.relay_chain_sync_oracle,
self.relay_chain_backend,
client,
))
}
}
/// Wait before announcing a block that a candidate message has been received for this block, then
/// add this message as justification for the block announcement.
///
+193 -174
View File
@@ -15,30 +15,33 @@
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use super::*;
use async_trait::async_trait;
use cumulus_relay_chain_interface::WaitError;
use cumulus_relay_chain_local::{check_block_in_chain, BlockCheckStatus};
use cumulus_test_service::runtime::{Block, Hash, Header};
use futures::{executor::block_on, poll, task::Poll};
use futures::{executor::block_on, poll, task::Poll, FutureExt, StreamExt};
use parking_lot::Mutex;
use polkadot_node_primitives::{SignedFullStatement, Statement};
use polkadot_primitives::v1::{
Block as PBlock, BlockNumber, CandidateCommitments, CandidateDescriptor, CandidateEvent,
CollatorPair, CommittedCandidateReceipt, CoreState, GroupRotationInfo, Hash as PHash, HeadData,
Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, OccupiedCoreAssumption,
ParachainHost, PersistedValidationData, PvfCheckStatement, ScrapedOnChainVotes, SessionIndex,
SessionInfo, SigningContext, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex,
ValidatorSignature,
Block as PBlock, CandidateCommitments, CandidateDescriptor, CollatorPair,
CommittedCandidateReceipt, Hash as PHash, HeadData, Id as ParaId, InboundDownwardMessage,
InboundHrmpMessage, OccupiedCoreAssumption, PersistedValidationData, SessionIndex,
SigningContext, ValidationCodeHash, ValidatorId,
};
use polkadot_service::Handle;
use polkadot_test_client::{
Client as PClient, ClientBlockImportExt, DefaultTestClientBuilderExt, FullBackend as PBackend,
InitPolkadotBlockBuilder, TestClientBuilder, TestClientBuilderExt,
};
use sp_api::{ApiRef, ProvideRuntimeApi};
use sc_client_api::{Backend, BlockchainEvents};
use sp_blockchain::HeaderBackend;
use sp_consensus::BlockOrigin;
use sp_core::{Pair, H256};
use sp_keyring::Sr25519Keyring;
use sp_keystore::{testing::KeyStore, SyncCryptoStore, SyncCryptoStorePtr};
use sp_runtime::RuntimeAppPublic;
use std::collections::BTreeMap;
use sp_state_machine::StorageValue;
use std::{collections::BTreeMap, time::Duration};
fn check_error(error: crate::BoxedError, check_error: impl Fn(&BlockAnnounceError) -> bool) {
let error = *error
@@ -50,31 +53,190 @@ fn check_error(error: crate::BoxedError, check_error: impl Fn(&BlockAnnounceErro
}
#[derive(Clone)]
struct DummyCollatorNetwork;
struct DummyRelayChainInterface {
data: Arc<Mutex<ApiData>>,
relay_client: Arc<PClient>,
relay_backend: Arc<PBackend>,
}
impl SyncOracle for DummyCollatorNetwork {
fn is_major_syncing(&mut self) -> bool {
impl DummyRelayChainInterface {
fn new() -> Self {
let builder = TestClientBuilder::new();
let relay_backend = builder.backend();
Self {
data: Arc::new(Mutex::new(ApiData {
validators: vec![Sr25519Keyring::Alice.public().into()],
has_pending_availability: false,
})),
relay_client: Arc::new(builder.build()),
relay_backend,
}
}
}
#[async_trait]
impl RelayChainInterface for DummyRelayChainInterface {
fn validators(
&self,
_: &cumulus_primitives_core::relay_chain::BlockId,
) -> Result<Vec<ValidatorId>, sp_api::ApiError> {
Ok(self.data.lock().validators.clone())
}
fn block_status(
&self,
block_id: cumulus_primitives_core::relay_chain::BlockId,
) -> Result<sp_blockchain::BlockStatus, sp_blockchain::Error> {
self.relay_backend.blockchain().status(block_id)
}
fn best_block_hash(&self) -> PHash {
self.relay_backend.blockchain().info().best_hash
}
fn retrieve_dmq_contents(&self, _: ParaId, _: PHash) -> Option<Vec<InboundDownwardMessage>> {
unimplemented!("Not needed for test")
}
fn retrieve_all_inbound_hrmp_channel_contents(
&self,
_: ParaId,
_: PHash,
) -> Option<BTreeMap<ParaId, Vec<InboundHrmpMessage>>> {
Some(BTreeMap::new())
}
fn persisted_validation_data(
&self,
_: &cumulus_primitives_core::relay_chain::BlockId,
_: ParaId,
_: OccupiedCoreAssumption,
) -> Result<Option<PersistedValidationData>, sp_api::ApiError> {
Ok(Some(PersistedValidationData {
parent_head: HeadData(default_header().encode()),
..Default::default()
}))
}
fn candidate_pending_availability(
&self,
_: &cumulus_primitives_core::relay_chain::BlockId,
_: ParaId,
) -> Result<Option<CommittedCandidateReceipt>, sp_api::ApiError> {
if self.data.lock().has_pending_availability {
Ok(Some(CommittedCandidateReceipt {
descriptor: CandidateDescriptor {
para_head: polkadot_parachain::primitives::HeadData(default_header().encode())
.hash(),
para_id: 0u32.into(),
relay_parent: PHash::random(),
collator: CollatorPair::generate().0.public(),
persisted_validation_data_hash: PHash::random().into(),
pov_hash: PHash::random(),
erasure_root: PHash::random(),
signature: sp_core::sr25519::Signature([0u8; 64]).into(),
validation_code_hash: ValidationCodeHash::from(PHash::random()),
},
commitments: CandidateCommitments {
upward_messages: Vec::new(),
horizontal_messages: Vec::new(),
new_validation_code: None,
head_data: HeadData(Vec::new()),
processed_downward_messages: 0,
hrmp_watermark: 0,
},
}))
} else {
Ok(None)
}
}
fn session_index_for_child(
&self,
_: &cumulus_primitives_core::relay_chain::BlockId,
) -> Result<SessionIndex, sp_api::ApiError> {
Ok(0)
}
fn import_notification_stream(&self) -> sc_client_api::ImportNotifications<PBlock> {
self.relay_client.import_notification_stream()
}
fn finality_notification_stream(&self) -> sc_client_api::FinalityNotifications<PBlock> {
self.relay_client.finality_notification_stream()
}
fn storage_changes_notification_stream(
&self,
filter_keys: Option<&[sc_client_api::StorageKey]>,
child_filter_keys: Option<
&[(sc_client_api::StorageKey, Option<Vec<sc_client_api::StorageKey>>)],
>,
) -> sc_client_api::blockchain::Result<sc_client_api::StorageEventStream<PHash>> {
self.relay_client
.storage_changes_notification_stream(filter_keys, child_filter_keys)
}
fn is_major_syncing(&self) -> bool {
false
}
fn is_offline(&mut self) -> bool {
unimplemented!("Not required in tests")
fn overseer_handle(&self) -> Option<Handle> {
unimplemented!("Not needed for test")
}
fn get_storage_by_key(
&self,
_: &polkadot_service::BlockId,
_: &[u8],
) -> Result<Option<StorageValue>, sp_blockchain::Error> {
unimplemented!("Not needed for test")
}
fn prove_read(
&self,
_: &polkadot_service::BlockId,
_: &Vec<Vec<u8>>,
) -> Result<Option<sc_client_api::StorageProof>, Box<dyn sp_state_machine::Error>> {
unimplemented!("Not needed for test")
}
async fn wait_for_block(
&self,
hash: PHash,
) -> Result<(), cumulus_relay_chain_interface::WaitError> {
let mut listener = match check_block_in_chain(
self.relay_backend.clone(),
self.relay_client.clone(),
hash,
)? {
BlockCheckStatus::InChain => return Ok(()),
BlockCheckStatus::Unknown(listener) => listener,
};
let mut timeout = futures_timer::Delay::new(Duration::from_secs(10)).fuse();
loop {
futures::select! {
_ = timeout => return Err(WaitError::Timeout(hash)),
evt = listener.next() => match evt {
Some(evt) if evt.hash == hash => return Ok(()),
// Not the event we waited on.
Some(_) => continue,
None => return Err(WaitError::ImportListenerClosed(hash)),
}
}
}
}
}
fn make_validator_and_api(
) -> (BlockAnnounceValidator<Block, TestApi, PBackend, PClient>, Arc<TestApi>) {
let api = Arc::new(TestApi::new());
) -> (BlockAnnounceValidator<Block, Arc<DummyRelayChainInterface>>, Arc<DummyRelayChainInterface>) {
let relay_chain_interface = Arc::new(DummyRelayChainInterface::new());
(
BlockAnnounceValidator::new(
api.clone(),
ParaId::from(56),
Box::new(DummyCollatorNetwork),
api.relay_backend.clone(),
api.relay_client.clone(),
),
api,
BlockAnnounceValidator::new(relay_chain_interface.clone(), ParaId::from(56)),
relay_chain_interface,
)
}
@@ -90,7 +252,7 @@ fn default_header() -> Header {
/// Same as [`make_gossip_message_and_header`], but using the genesis header as relay parent.
async fn make_gossip_message_and_header_using_genesis(
api: Arc<TestApi>,
api: Arc<DummyRelayChainInterface>,
validator_index: u32,
) -> (CollationSecondedSignal, Header) {
let relay_parent = api.relay_client.hash(0).ok().flatten().expect("Genesis hash exists");
@@ -99,7 +261,7 @@ async fn make_gossip_message_and_header_using_genesis(
}
async fn make_gossip_message_and_header(
api: Arc<TestApi>,
relay_chain_interface: Arc<DummyRelayChainInterface>,
relay_parent: H256,
validator_index: u32,
) -> (CollationSecondedSignal, Header) {
@@ -110,8 +272,9 @@ async fn make_gossip_message_and_header(
Some(&Sr25519Keyring::Alice.to_seed()),
)
.unwrap();
let session_index =
api.runtime_api().session_index_for_child(&BlockId::Hash(relay_parent)).unwrap();
let session_index = relay_chain_interface
.session_index_for_child(&BlockId::Hash(relay_parent))
.unwrap();
let signing_context = SigningContext { parent_hash: relay_parent, session_index };
let header = default_header();
@@ -292,8 +455,7 @@ fn check_statement_seconded() {
Some(&Sr25519Keyring::Alice.to_seed()),
)
.unwrap();
let session_index =
api.runtime_api().session_index_for_child(&BlockId::Hash(relay_parent)).unwrap();
let session_index = api.session_index_for_child(&BlockId::Hash(relay_parent)).unwrap();
let signing_context = SigningContext { parent_hash: relay_parent, session_index };
let statement = Statement::Valid(Default::default());
@@ -397,146 +559,3 @@ struct ApiData {
validators: Vec<ValidatorId>,
has_pending_availability: bool,
}
struct TestApi {
data: Arc<Mutex<ApiData>>,
relay_client: Arc<PClient>,
relay_backend: Arc<PBackend>,
}
impl TestApi {
fn new() -> Self {
let builder = TestClientBuilder::new();
let relay_backend = builder.backend();
Self {
data: Arc::new(Mutex::new(ApiData {
validators: vec![Sr25519Keyring::Alice.public().into()],
has_pending_availability: false,
})),
relay_client: Arc::new(builder.build()),
relay_backend,
}
}
}
#[derive(Default)]
struct RuntimeApi {
data: Arc<Mutex<ApiData>>,
}
impl ProvideRuntimeApi<PBlock> for TestApi {
type Api = RuntimeApi;
fn runtime_api<'a>(&'a self) -> ApiRef<'a, Self::Api> {
RuntimeApi { data: self.data.clone() }.into()
}
}
sp_api::mock_impl_runtime_apis! {
impl ParachainHost<PBlock> for RuntimeApi {
fn validators(&self) -> Vec<ValidatorId> {
self.data.lock().validators.clone()
}
fn validator_groups(&self) -> (Vec<Vec<ValidatorIndex>>, GroupRotationInfo<BlockNumber>) {
(Vec::new(), GroupRotationInfo { session_start_block: 0, group_rotation_frequency: 0, now: 0 })
}
fn availability_cores(&self) -> Vec<CoreState<PHash>> {
Vec::new()
}
fn persisted_validation_data(
&self,
_: ParaId,
_: OccupiedCoreAssumption,
) -> Option<PersistedValidationData<PHash, BlockNumber>> {
Some(PersistedValidationData {
parent_head: HeadData(default_header().encode()),
..Default::default()
})
}
fn session_index_for_child(&self) -> SessionIndex {
0
}
fn validation_code(&self, _: ParaId, _: OccupiedCoreAssumption) -> Option<ValidationCode> {
None
}
fn candidate_pending_availability(&self, _: ParaId) -> Option<CommittedCandidateReceipt<PHash>> {
if self.data.lock().has_pending_availability {
Some(CommittedCandidateReceipt {
descriptor: CandidateDescriptor {
para_head: HeadData(
default_header().encode(),
).hash(),
para_id: 0u32.into(),
relay_parent: PHash::random(),
collator: CollatorPair::generate().0.public(),
persisted_validation_data_hash: PHash::random().into(),
pov_hash: PHash::random(),
erasure_root: PHash::random(),
signature: sp_core::sr25519::Signature([0u8; 64]).into(),
validation_code_hash: ValidationCodeHash::from(PHash::random()),
},
commitments: CandidateCommitments {
upward_messages: Vec::new(),
horizontal_messages: Vec::new(),
new_validation_code: None,
head_data: HeadData(Vec::new()),
processed_downward_messages: 0,
hrmp_watermark: 0
}
})
} else {
None
}
}
fn candidate_events(&self) -> Vec<CandidateEvent<PHash>> {
Vec::new()
}
fn session_info(_: SessionIndex) -> Option<SessionInfo> {
None
}
fn check_validation_outputs(_: ParaId, _: CandidateCommitments) -> bool {
false
}
fn dmq_contents(_: ParaId) -> Vec<InboundDownwardMessage<BlockNumber>> {
Vec::new()
}
fn inbound_hrmp_channels_contents(
_: ParaId,
) -> BTreeMap<ParaId, Vec<InboundHrmpMessage<BlockNumber>>> {
BTreeMap::new()
}
fn assumed_validation_data(
_: ParaId,
_: Hash,
) -> Option<(PersistedValidationData<Hash, BlockNumber>, ValidationCodeHash)> {
None
}
fn validation_code_by_hash(_: ValidationCodeHash) -> Option<ValidationCode> {
None
}
fn on_chain_votes() -> Option<ScrapedOnChainVotes<Hash>> {
None
}
fn submit_pvf_check_statement(_: PvfCheckStatement, _: ValidatorSignature) {}
fn pvfs_require_precheck() -> Vec<ValidationCodeHash> {
Vec::new()
}
}
}
@@ -1,235 +0,0 @@
// Copyright 2020-2021 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! Provides the [`WaitOnRelayChainBlock`] type.
use futures::{future::ready, Future, FutureExt, StreamExt};
use polkadot_primitives::v1::{Block as PBlock, Hash as PHash};
use sc_client_api::{
blockchain::{self, BlockStatus, HeaderBackend},
Backend, BlockchainEvents,
};
use sp_runtime::generic::BlockId;
use std::{sync::Arc, time::Duration};
/// The timeout in seconds after that the waiting for a block should be aborted.
const TIMEOUT_IN_SECONDS: u64 = 6;
/// Custom error type used by [`WaitOnRelayChainBlock`].
#[derive(Debug, derive_more::Display)]
pub enum Error {
#[display(fmt = "Timeout while waiting for relay-chain block `{}` to be imported.", _0)]
Timeout(PHash),
#[display(
fmt = "Import listener closed while waiting for relay-chain block `{}` to be imported.",
_0
)]
ImportListenerClosed(PHash),
#[display(
fmt = "Blockchain returned an error while waiting for relay-chain block `{}` to be imported: {:?}",
_0,
_1
)]
BlockchainError(PHash, blockchain::Error),
}
/// A helper to wait for a given relay chain block in an async way.
///
/// The caller needs to pass the hash of a block it waits for and the function will return when the
/// block is available or an error occurred.
///
/// The waiting for the block is implemented as follows:
///
/// 1. Get a read lock on the import lock from the backend.
///
/// 2. Check if the block is already imported. If yes, return from the function.
///
/// 3. If the block isn't imported yet, add an import notification listener.
///
/// 4. Poll the import notification listener until the block is imported or the timeout is fired.
///
/// The timeout is set to 6 seconds. This should be enough time to import the block in the current
/// round and if not, the new round of the relay chain already started anyway.
pub struct WaitOnRelayChainBlock<B, BCE> {
block_chain_events: Arc<BCE>,
backend: Arc<B>,
}
impl<B, BCE> Clone for WaitOnRelayChainBlock<B, BCE> {
fn clone(&self) -> Self {
Self { backend: self.backend.clone(), block_chain_events: self.block_chain_events.clone() }
}
}
impl<B, BCE> WaitOnRelayChainBlock<B, BCE> {
/// Creates a new instance of `Self`.
pub fn new(backend: Arc<B>, block_chain_events: Arc<BCE>) -> Self {
Self { backend, block_chain_events }
}
}
impl<B, BCE> WaitOnRelayChainBlock<B, BCE>
where
B: Backend<PBlock>,
BCE: BlockchainEvents<PBlock>,
{
pub fn wait_on_relay_chain_block(
&self,
hash: PHash,
) -> impl Future<Output = Result<(), Error>> {
let _lock = self.backend.get_import_lock().read();
match self.backend.blockchain().status(BlockId::Hash(hash)) {
Ok(BlockStatus::InChain) => return ready(Ok(())).boxed(),
Err(err) => return ready(Err(Error::BlockchainError(hash, err))).boxed(),
_ => {},
}
let mut listener = self.block_chain_events.import_notification_stream();
// Now it is safe to drop the lock, even when the block is now imported, it should show
// up in our registered listener.
drop(_lock);
let mut timeout = futures_timer::Delay::new(Duration::from_secs(TIMEOUT_IN_SECONDS)).fuse();
async move {
loop {
futures::select! {
_ = timeout => return Err(Error::Timeout(hash)),
evt = listener.next() => match evt {
Some(evt) if evt.hash == hash => return Ok(()),
// Not the event we waited on.
Some(_) => continue,
None => return Err(Error::ImportListenerClosed(hash)),
}
}
}
}
.boxed()
}
}
#[cfg(test)]
mod tests {
use super::*;
use polkadot_test_client::{
construct_transfer_extrinsic, BlockBuilderExt, Client, ClientBlockImportExt,
DefaultTestClientBuilderExt, ExecutionStrategy, FullBackend, InitPolkadotBlockBuilder,
TestClientBuilder, TestClientBuilderExt,
};
use sp_consensus::BlockOrigin;
use sp_runtime::traits::Block as BlockT;
use futures::{executor::block_on, poll, task::Poll};
fn build_client_backend_and_block() -> (Arc<Client>, Arc<FullBackend>, PBlock) {
let builder =
TestClientBuilder::new().set_execution_strategy(ExecutionStrategy::NativeWhenPossible);
let backend = builder.backend();
let client = Arc::new(builder.build());
let block_builder = client.init_polkadot_block_builder();
let block = block_builder.build().expect("Finalizes the block").block;
(client, backend, block)
}
#[test]
fn returns_directly_for_available_block() {
let (mut client, backend, block) = build_client_backend_and_block();
let hash = block.hash();
block_on(client.import(BlockOrigin::Own, block)).expect("Imports the block");
let wait = WaitOnRelayChainBlock::new(backend, client);
block_on(async move {
// Should be ready on the first poll
assert!(matches!(poll!(wait.wait_on_relay_chain_block(hash)), Poll::Ready(Ok(()))));
});
}
#[test]
fn resolve_after_block_import_notification_was_received() {
let (mut client, backend, block) = build_client_backend_and_block();
let hash = block.hash();
let wait = WaitOnRelayChainBlock::new(backend, client.clone());
block_on(async move {
let mut future = wait.wait_on_relay_chain_block(hash);
// As the block is not yet imported, the first poll should return `Pending`
assert!(poll!(&mut future).is_pending());
// Import the block that should fire the notification
client.import(BlockOrigin::Own, block).await.expect("Imports the block");
// Now it should have received the notification and report that the block was imported
assert!(matches!(poll!(future), Poll::Ready(Ok(()))));
});
}
#[test]
fn wait_for_block_time_out_when_block_is_not_imported() {
let (client, backend, block) = build_client_backend_and_block();
let hash = block.hash();
let wait = WaitOnRelayChainBlock::new(backend, client.clone());
assert!(matches!(block_on(wait.wait_on_relay_chain_block(hash)), Err(Error::Timeout(_))));
}
#[test]
fn do_not_resolve_after_different_block_import_notification_was_received() {
let (mut client, backend, block) = build_client_backend_and_block();
let hash = block.hash();
let ext = construct_transfer_extrinsic(
&*client,
sp_keyring::Sr25519Keyring::Alice,
sp_keyring::Sr25519Keyring::Bob,
1000,
);
let mut block_builder = client.init_polkadot_block_builder();
// Push an extrinsic to get a different block hash.
block_builder.push_polkadot_extrinsic(ext).expect("Push extrinsic");
let block2 = block_builder.build().expect("Build second block").block;
let hash2 = block2.hash();
let wait = WaitOnRelayChainBlock::new(backend, client.clone());
block_on(async move {
let mut future = wait.wait_on_relay_chain_block(hash);
let mut future2 = wait.wait_on_relay_chain_block(hash2);
// As the block is not yet imported, the first poll should return `Pending`
assert!(poll!(&mut future).is_pending());
assert!(poll!(&mut future2).is_pending());
// Import the block that should fire the notification
client.import(BlockOrigin::Own, block2).await.expect("Imports the second block");
// The import notification of the second block should not make this one finish
assert!(poll!(&mut future).is_pending());
// Now it should have received the notification and report that the block was imported
assert!(matches!(poll!(future2), Poll::Ready(Ok(()))));
client.import(BlockOrigin::Own, block).await.expect("Imports the first block");
// Now it should be ready
assert!(matches!(poll!(future), Poll::Ready(Ok(()))));
});
}
}