Wait for relay chain block import before validatiing a block announcement (#227)

* Start with something

* Whatever

* Update

* MOARE

* Make cumulus-network compile and tests work

* Update more and fixes

* More stuff

* More fixes

* Make collator build

* Make test almost work

* Remove contracts runtime

* More test work

* Make service compile

* Fix test-service

* Fix test client

* More fixes

* Fix collator test

* Fix network tests (again)

* Make everything compile, finally

* Fix tests

* Write test that should fail

* Add `WaitOnRelayChainBlock`

* Update git versions

* Make it all work

* Update logging

* Switch to provided method for pushing an extrinsic

* Try to debug CI

* Aaaa

* Only use Debug

* Updates

* Use native execution to hopefully make CI happy...
This commit is contained in:
Bastian Köcher
2020-11-23 00:21:02 +01:00
committed by GitHub
parent b11ec7ea9f
commit e5b4e8cae7
17 changed files with 1310 additions and 683 deletions
+219 -175
View File
@@ -22,7 +22,9 @@
#[cfg(test)]
mod tests;
mod wait_on_relay_chain_block;
use sc_client_api::{Backend, BlockchainEvents};
use sp_api::ProvideRuntimeApi;
use sp_blockchain::{Error as ClientError, HeaderBackend};
use sp_consensus::{
@@ -32,7 +34,7 @@ use sp_consensus::{
use sp_core::traits::SpawnNamed;
use sp_runtime::{
generic::BlockId,
traits::{Block as BlockT, Header as HeaderT},
traits::{Block as BlockT, HashFor, Header as HeaderT},
};
use polkadot_node_primitives::{SignedFullStatement, Statement};
@@ -54,6 +56,10 @@ use log::{trace, warn};
use std::{marker::PhantomData, pin::Pin, sync::Arc};
use wait_on_relay_chain_block::WaitOnRelayChainBlock;
type BlockAnnounceError = Box<dyn std::error::Error + Send>;
/// Parachain specific block announce validator.
///
/// This block announce validator is required if the parachain is running
@@ -81,260 +87,298 @@ use std::{marker::PhantomData, pin::Pin, sync::Arc};
/// 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<B, P> {
phantom: PhantomData<B>,
polkadot_client: Arc<P>,
pub struct BlockAnnounceValidator<Block, P, B, BCE> {
phantom: PhantomData<Block>,
relay_chain_client: Arc<P>,
relay_chain_backend: Arc<B>,
para_id: ParaId,
polkadot_sync_oracle: Box<dyn SyncOracle + Send>,
relay_chain_sync_oracle: Box<dyn SyncOracle + Send>,
wait_on_relay_chain_block: WaitOnRelayChainBlock<B, BCE>,
}
impl<B, P> BlockAnnounceValidator<B, P> {
impl<Block, P, B, BCE> BlockAnnounceValidator<Block, P, B, BCE> {
/// Create a new [`BlockAnnounceValidator`].
pub fn new(
polkadot_client: Arc<P>,
relay_chain_client: Arc<P>,
para_id: ParaId,
polkadot_sync_oracle: Box<dyn SyncOracle + Send>,
relay_chain_sync_oracle: Box<dyn SyncOracle + Send>,
relay_chain_backend: Arc<B>,
relay_chain_blockchain_events: Arc<BCE>,
) -> Self {
Self {
phantom: Default::default(),
polkadot_client,
relay_chain_client,
para_id,
polkadot_sync_oracle,
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<B: BlockT, P> BlockAnnounceValidatorT<B> for BlockAnnounceValidator<B, P>
impl<Block: BlockT, P, B, BCE> BlockAnnounceValidator<Block, P, B, BCE>
where
P: ProvideRuntimeApi<PBlock> + HeaderBackend<PBlock> + 'static,
P: ProvideRuntimeApi<PBlock> + Send + Sync + 'static,
P::Api: ParachainHost<PBlock>,
B: Backend<PBlock> + 'static,
// Rust bug: https://github.com/rust-lang/rust/issues/24159
sc_client_api::StateBackendFor<B, PBlock>: sc_client_api::StateBackend<HashFor<PBlock>>,
{
/// Handle a block announcement with empty data (no statement) attached to it.
fn handle_empty_block_announce_data(
&self,
header: Block::Header,
) -> impl Future<Output = Result<Validation, BlockAnnounceError>> {
let relay_chain_client = self.relay_chain_client.clone();
let relay_chain_backend = self.relay_chain_backend.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 block_number = header.number();
let local_validation_data = relay_chain_client
.runtime_api()
.persisted_validation_data(
&runtime_api_block_id,
para_id,
OccupiedCoreAssumption::TimedOut,
)
.map_err(|e| Box::new(ClientError::Msg(format!("{:?}", e))) as Box<_>)?
.ok_or_else(|| {
Box::new(ClientError::Msg(
"Could not find parachain head in relay chain".into(),
)) as Box<_>
})?;
let parent_head = Block::Header::decode(&mut &local_validation_data.parent_head.0[..])
.map_err(|e| {
Box::new(ClientError::Msg(format!(
"Failed to decode parachain head: {:?}",
e
))) as Box<_>
})?;
let known_best_number = parent_head.number();
if block_number >= known_best_number {
trace!(
target: "cumulus-network",
"validation failed because a justification is needed if the block at the top of the chain."
);
Ok(Validation::Failure)
} else {
Ok(Validation::Success { is_new_best: false })
}
}
}
}
impl<Block: BlockT, P, B, BCE> BlockAnnounceValidatorT<Block>
for BlockAnnounceValidator<Block, P, B, BCE>
where
P: ProvideRuntimeApi<PBlock> + Send + Sync + 'static,
P::Api: ParachainHost<PBlock>,
B: Backend<PBlock> + 'static,
BCE: BlockchainEvents<PBlock> + 'static + Send + Sync,
// Rust bug: https://github.com/rust-lang/rust/issues/24159
sc_client_api::StateBackendFor<B, PBlock>: sc_client_api::StateBackend<HashFor<PBlock>>,
{
fn validate(
&mut self,
header: &B::Header,
header: &Block::Header,
mut data: &[u8],
) -> Pin<Box<dyn Future<Output = Result<Validation, Box<dyn std::error::Error + Send>>> + Send>>
{
if self.polkadot_sync_oracle.is_major_syncing() {
) -> Pin<Box<dyn Future<Output = Result<Validation, BlockAnnounceError>> + Send>> {
if self.relay_chain_sync_oracle.is_major_syncing() {
return ready(Ok(Validation::Success { is_new_best: false })).boxed();
}
let runtime_api = self.polkadot_client.runtime_api();
let polkadot_info = self.polkadot_client.info();
if data.is_empty() {
let polkadot_client = self.polkadot_client.clone();
let header = header.clone();
let para_id = self.para_id;
return async move {
// Check if block is equal or higher than best (this requires a justification)
let runtime_api_block_id = BlockId::Hash(polkadot_info.best_hash);
let block_number = header.number();
let local_validation_data = polkadot_client
.runtime_api()
.persisted_validation_data(
&runtime_api_block_id,
para_id,
OccupiedCoreAssumption::TimedOut,
)
.map_err(|e| Box::new(ClientError::Msg(format!("{:?}", e))) as Box<_>)?
.ok_or_else(|| {
Box::new(ClientError::Msg(
"Could not find parachain head in relay chain".into(),
)) as Box<_>
})?;
let parent_head = B::Header::decode(&mut &local_validation_data.parent_head.0[..])
.map_err(|e| {
Box::new(ClientError::Msg(format!(
"Failed to decode parachain head: {:?}",
e
))) as Box<_>
})?;
let known_best_number = parent_head.number();
if block_number >= known_best_number {
trace!(
target: "cumulus-network",
"validation failed because a justification is needed if the block at the top of the chain."
);
Ok(Validation::Failure)
} else {
Ok(Validation::Success { is_new_best: false })
}
}
.boxed();
return self
.handle_empty_block_announce_data(header.clone())
.boxed();
}
let signed_stmt = match SignedFullStatement::decode(&mut data) {
Ok(r) => r,
Err(_) => return ready(Err(Box::new(ClientError::BadJustification(
Err(_) => return ready(Err(Box::new(ClientError::Msg(
"cannot decode block announcement justification, must be a `SignedFullStatement`"
.to_string(),
.into(),
)) as Box<_>))
.boxed(),
};
// Check statement is a candidate statement.
let candidate_receipt = match signed_stmt.payload() {
Statement::Seconded(ref candidate_receipt) => candidate_receipt,
_ => {
return ready(Err(Box::new(ClientError::BadJustification(
"block announcement justification must be a `Statement::Seconded`".to_string(),
)) as Box<_>))
.boxed()
}
};
let relay_chain_client = self.relay_chain_client.clone();
let header_encoded = header.encode();
let wait_on_relay_chain_block = self.wait_on_relay_chain_block.clone();
// Check that the relay chain parent of the block is the relay chain head
let best_number = polkadot_info.best_number;
let validator_index = signed_stmt.validator_index();
let relay_parent = &candidate_receipt.descriptor.relay_parent;
async move {
// Check statement is a candidate statement.
let candidate_receipt = match signed_stmt.payload() {
Statement::Seconded(ref candidate_receipt) => candidate_receipt,
_ => {
return Err(Box::new(ClientError::Msg(
"block announcement justification must be a `Statement::Seconded`".into(),
)) as Box<_>)
}
};
match self.polkadot_client.number(*relay_parent) {
Err(err) => {
return ready(Err(Box::new(ClientError::Backend(format!(
"could not find block number for {}: {}",
relay_parent, err,
))) as Box<_>))
.boxed();
// Check the header in the candidate_receipt match header given header.
if header_encoded != candidate_receipt.commitments.head_data.0 {
return Err(Box::new(ClientError::Msg(
"block announcement header does not match the one justified".into(),
)) as Box<_>);
}
Ok(Some(x)) if x == best_number => {}
Ok(None) => {
return ready(Err(
Box::new(ClientError::UnknownBlock(relay_parent.to_string())) as Box<_>,
))
.boxed();
}
Ok(Some(_)) => {
trace!(
target: "cumulus-network",
"validation failed because the relay chain parent ({}) is not the relay chain \
head ({})",
relay_parent,
best_number,
);
return ready(Ok(Validation::Failure)).boxed();
let relay_parent = &candidate_receipt.descriptor.relay_parent;
wait_on_relay_chain_block
.wait_on_relay_chain_block(*relay_parent)
.await
.map_err(|e| Box::new(ClientError::Msg(e.to_string())) as Box<_>)?;
let runtime_api = relay_chain_client.runtime_api();
let validator_index = signed_stmt.validator_index();
let runtime_api_block_id = BlockId::Hash(*relay_parent);
let session_index = match runtime_api.session_index_for_child(&runtime_api_block_id) {
Ok(r) => r,
Err(e) => {
return Err(Box::new(ClientError::Msg(format!("{:?}", e))) as Box<_>);
}
};
let signing_context = SigningContext {
parent_hash: *relay_parent,
session_index,
};
// Check that the signer is a legit validator.
let authorities = match runtime_api.validators(&runtime_api_block_id) {
Ok(r) => r,
Err(e) => {
return Err(Box::new(ClientError::Msg(format!("{:?}", e))) as Box<_>);
}
};
let signer = match authorities.get(validator_index as usize) {
Some(r) => r,
None => {
return Err(Box::new(ClientError::Msg(
"block accouncement justification signer is a validator index out of bound"
.to_string(),
)) as Box<_>);
}
};
// Check statement is correctly signed.
if signed_stmt
.check_signature(&signing_context, &signer)
.is_err()
{
return Err(Box::new(ClientError::Msg(
"block announcement justification signature is invalid".to_string(),
)) as Box<_>);
}
Ok(Validation::Success { is_new_best: true })
}
let runtime_api_block_id = BlockId::Hash(*relay_parent);
let session_index = match runtime_api.session_index_for_child(&runtime_api_block_id) {
Ok(r) => r,
Err(e) => {
return ready(Err(Box::new(ClientError::Msg(format!("{:?}", e))) as Box<_>)).boxed()
}
};
let signing_context = SigningContext {
parent_hash: *relay_parent,
session_index,
};
// Check that the signer is a legit validator.
let authorities = match runtime_api.validators(&runtime_api_block_id) {
Ok(r) => r,
Err(e) => {
return ready(Err(Box::new(ClientError::Msg(format!("{:?}", e))) as Box<_>)).boxed()
}
};
let signer = match authorities.get(validator_index as usize) {
Some(r) => r,
None => {
return ready(Err(Box::new(ClientError::BadJustification(
"block accouncement justification signer is a validator index out of bound"
.to_string(),
)) as Box<_>))
.boxed()
}
};
// Check statement is correctly signed.
if signed_stmt
.check_signature(&signing_context, &signer)
.is_err()
{
return ready(Err(Box::new(ClientError::BadJustification(
"block announced justification signature is invalid".to_string(),
)) as Box<_>))
.boxed();
}
// Check the header in the candidate_receipt match header given header.
if header.encode() != candidate_receipt.commitments.head_data.0 {
return ready(Err(Box::new(ClientError::BadJustification(
"block announced header does not match the one justified".to_string(),
)) as Box<_>))
.boxed();
}
ready(Ok(Validation::Success { is_new_best: true })).boxed()
.boxed()
}
}
/// Build a block announce validator instance.
///
/// Returns a boxed [`BlockAnnounceValidator`].
pub fn build_block_announce_validator<B: BlockT>(
polkadot_client: polkadot_service::Client,
pub fn build_block_announce_validator<Block: BlockT, B>(
relay_chain_client: polkadot_service::Client,
para_id: ParaId,
polkadot_sync_oracle: Box<dyn SyncOracle + Send>,
) -> Box<dyn BlockAnnounceValidatorT<B> + Send> {
BlockAnnounceValidatorBuilder::new(polkadot_client, para_id, polkadot_sync_oracle).build()
relay_chain_sync_oracle: Box<dyn SyncOracle + Send>,
relay_chain_backend: Arc<B>,
) -> Box<dyn BlockAnnounceValidatorT<Block> + Send>
where
B: Backend<PBlock> + Send + 'static,
// Rust bug: https://github.com/rust-lang/rust/issues/24159
sc_client_api::StateBackendFor<B, PBlock>: sc_client_api::StateBackend<HashFor<PBlock>>,
{
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 Polkadot client instance, the builder takes a [`polkadot_service::Client`]
/// a concrete relay chain client instance, the builder takes a [`polkadot_service::Client`]
/// that wraps this concrete instanace. By using [`polkadot_service::ExecuteWithClient`]
/// the builder gets access to this concrete instance.
struct BlockAnnounceValidatorBuilder<B> {
phantom: PhantomData<B>,
polkadot_client: polkadot_service::Client,
struct BlockAnnounceValidatorBuilder<Block, B> {
phantom: PhantomData<Block>,
relay_chain_client: polkadot_service::Client,
para_id: ParaId,
polkadot_sync_oracle: Box<dyn SyncOracle + Send>,
relay_chain_sync_oracle: Box<dyn SyncOracle + Send>,
relay_chain_backend: Arc<B>,
}
impl<B: BlockT> BlockAnnounceValidatorBuilder<B> {
impl<Block: BlockT, B> BlockAnnounceValidatorBuilder<Block, B>
where
B: Backend<PBlock> + Send + 'static,
// Rust bug: https://github.com/rust-lang/rust/issues/24159
sc_client_api::StateBackendFor<B, PBlock>: sc_client_api::StateBackend<HashFor<PBlock>>,
{
/// Create a new instance of the builder.
fn new(
polkadot_client: polkadot_service::Client,
relay_chain_client: polkadot_service::Client,
para_id: ParaId,
polkadot_sync_oracle: Box<dyn SyncOracle + Send>,
relay_chain_sync_oracle: Box<dyn SyncOracle + Send>,
relay_chain_backend: Arc<B>,
) -> Self {
Self {
polkadot_client,
relay_chain_client,
para_id,
polkadot_sync_oracle,
relay_chain_sync_oracle,
relay_chain_backend,
phantom: PhantomData,
}
}
/// Build the block announce validator.
fn build(self) -> Box<dyn BlockAnnounceValidatorT<B> + Send> {
self.polkadot_client.clone().execute_with(self)
fn build(self) -> Box<dyn BlockAnnounceValidatorT<Block> + Send> {
self.relay_chain_client.clone().execute_with(self)
}
}
impl<B: BlockT> polkadot_service::ExecuteWithClient for BlockAnnounceValidatorBuilder<B> {
type Output = Box<dyn BlockAnnounceValidatorT<B> + Send>;
impl<Block: BlockT, B> polkadot_service::ExecuteWithClient
for BlockAnnounceValidatorBuilder<Block, B>
where
B: Backend<PBlock> + Send + 'static,
// Rust bug: https://github.com/rust-lang/rust/issues/24159
sc_client_api::StateBackendFor<B, PBlock>: sc_client_api::StateBackend<HashFor<PBlock>>,
{
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: sc_client_api::Backend<PBlock>,
PBackend: Backend<PBlock>,
PBackend::State: sp_api::StateBackend<sp_runtime::traits::BlakeTwo256>,
Api: polkadot_service::RuntimeApiCollection<StateBackend = PBackend::State>,
PClient: polkadot_service::AbstractClient<PBlock, PBackend, Api = Api> + 'static,
{
Box::new(BlockAnnounceValidator::new(
client,
client.clone(),
self.para_id,
self.polkadot_sync_oracle,
self.relay_chain_sync_oracle,
self.relay_chain_backend,
client,
))
}
}