,
// Unused for now. Will be plugged in with a later PR.
_authorities_tracker: AuthoritiesTracker,
}
impl
Verifier
where
P: Pair,
P::Signature: Codec,
P::Public: Codec + Debug,
Block: BlockT,
Client: ProvideRuntimeApi + Send + Sync,
>::Api: BlockBuilderApi + AuraApi,
CIDP: CreateInherentDataProviders,
{
/// Creates a new Verifier instance for handling teyrchain block import verification in Aura
/// consensus.
pub fn new(
client: Arc,
inherent_data_provider: CIDP,
telemetry: Option,
) -> Self {
Self {
client: client.clone(),
create_inherent_data_providers: inherent_data_provider,
defender: Mutex::new(NaiveEquivocationDefender::default()),
telemetry,
_authorities_tracker: AuthoritiesTracker::new(client),
}
}
}
#[async_trait::async_trait]
impl VerifierT for Verifier
where
P: Pair,
P::Signature: Codec,
P::Public: Codec + Debug,
Block: BlockT,
Client: HeaderBackend
+ HeaderMetadata
+ ProvideRuntimeApi
+ Send
+ Sync,
>::Api: BlockBuilderApi + AuraApi,
CIDP: CreateInherentDataProviders,
{
async fn verify(
&self,
mut block_params: BlockImportParams,
) -> Result, String> {
// Skip checks that include execution, if being told so, or when importing only state.
//
// This is done for example when gap syncing and it is expected that the block after the gap
// was checked/chosen properly, e.g. by warp syncing to this block using a finality proof.
if block_params.state_action.skip_execution_checks() || block_params.with_state() {
block_params.fork_choice = Some(ForkChoiceStrategy::Custom(block_params.with_state()));
return Ok(block_params);
}
let post_hash = block_params.header.hash();
let parent_hash = *block_params.header.parent_hash();
// check seal and update pre-hash/post-hash
{
let authorities = aura_internal::fetch_authorities(self.client.as_ref(), parent_hash)
.map_err(|e| {
format!("Could not fetch authorities at {:?}: {}", parent_hash, e)
})?;
let slot_duration = self
.client
.runtime_api()
.slot_duration(parent_hash)
.map_err(|e| e.to_string())?;
let slot_now = slot_now(slot_duration);
let res = aura_internal::check_header_slot_and_seal::(
slot_now,
block_params.header,
&authorities,
);
match res {
Ok((pre_header, slot, seal_digest)) => {
telemetry!(
self.telemetry;
CONSENSUS_TRACE;
"aura.checked_and_importing";
"pre_header" => ?pre_header,
);
// We need some kind of identifier for the relay parent, in the worst case we
// take the all `0` hash.
let relay_parent =
match CumulusDigestItem::find_relay_block_identifier(pre_header.digest()) {
None => Default::default(),
Some(RelayBlockIdentifier::ByHash(h))
| Some(RelayBlockIdentifier::ByStorageRoot {
storage_root: h, ..
}) => h,
};
block_params.header = pre_header;
block_params.post_digests.push(seal_digest);
block_params.fork_choice = Some(ForkChoiceStrategy::LongestChain);
block_params.post_hash = Some(post_hash);
// Check for and reject egregious amounts of equivocations.
//
// If the `origin` is `ConsensusBroadcast`, we ignore the result of the
// equivocation check. This `origin` is for example used by pov-recovery.
if self.defender.lock().insert_and_check(
slot,
*block_params.header.number(),
relay_parent,
) && !matches!(block_params.origin, BlockOrigin::ConsensusBroadcast)
{
return Err(format!(
"Rejecting block {:?} due to excessive equivocations at slot",
post_hash,
));
}
},
Err(aura_internal::SealVerificationError::Deferred(hdr, slot)) => {
telemetry!(
self.telemetry;
CONSENSUS_DEBUG;
"aura.header_too_far_in_future";
"hash" => ?post_hash,
"a" => ?hdr,
"b" => ?slot,
);
return Err(format!(
"Rejecting block ({:?}) from future slot {:?}",
post_hash, slot
));
},
Err(e) => {
return Err(format!(
"Rejecting block ({:?}) with invalid seal ({:?})",
post_hash, e
))
},
}
}
// Check inherents.
if let Some(body) = block_params.body.clone() {
let block = Block::new(block_params.header.clone(), body);
let create_inherent_data_providers = self
.create_inherent_data_providers
.create_inherent_data_providers(parent_hash, ())
.await
.map_err(|e| format!("Could not create inherent data {:?}", e))?;
pezsp_block_builder::check_inherents(
self.client.clone(),
parent_hash,
block,
&create_inherent_data_providers,
)
.await
.map_err(|e| format!("Error checking block inherents {:?}", e))?;
}
Ok(block_params)
}
}
fn slot_now(slot_duration: SlotDuration) -> Slot {
let timestamp = pezsp_timestamp::InherentDataProvider::from_system_time().timestamp();
Slot::from_timestamp(timestamp, slot_duration)
}
/// Start an import queue for a Pezcumulus node which checks blocks' seals and inherent data.
///
/// Pass in only inherent data providers which don't include aura or teyrchain consensus inherents,
/// e.g. things like timestamp and custom inherents for the runtime.
///
/// The others are generated explicitly internally.
///
/// This should only be used for runtimes where the runtime does not check all inherents and
/// seals in `execute_block` (see )
pub fn fully_verifying_import_queue(
client: Arc,
block_import: I,
create_inherent_data_providers: CIDP,
spawner: &impl pezsp_core::traits::SpawnEssentialNamed,
registry: Option<&prometheus_endpoint::Registry>,
telemetry: Option,
) -> BasicQueue
where
P: Pair + 'static,
P::Signature: Codec,
P::Public: Codec + Debug,
I: BlockImport
+ TeyrchainBlockImportMarker
+ Send
+ Sync
+ 'static,
Client: HeaderBackend
+ HeaderMetadata
+ ProvideRuntimeApi
+ Send
+ Sync
+ 'static,
>::Api: BlockBuilderApi + AuraApi,
CIDP: CreateInherentDataProviders + 'static,
{
let verifier = Verifier:: {
client: client.clone(),
create_inherent_data_providers,
defender: Mutex::new(NaiveEquivocationDefender::default()),
telemetry,
_authorities_tracker: AuthoritiesTracker::new(client.clone()),
};
BasicQueue::new(verifier, Box::new(block_import), None, spawner, registry)
}
#[cfg(test)]
mod test {
use super::*;
use codec::Encode;
use futures::FutureExt;
use pezcumulus_test_client::{
runtime::Block, seal_block, Client, InitBlockBuilder, TestClientBuilder,
TestClientBuilderExt,
};
use pezcumulus_test_relay_sproof_builder::RelayStateSproofBuilder;
use pezkuwi_primitives::{HeadData, PersistedValidationData};
use pezsc_client_api::HeaderBackend;
use pezsp_consensus_aura::sr25519;
use pezsp_tracing::try_init_simple;
use std::{collections::HashSet, sync::Arc};
#[test]
fn import_equivocated_blocks_from_recovery() {
try_init_simple();
let client = Arc::new(TestClientBuilder::default().build());
let verifier = Verifier:: {
client: client.clone(),
create_inherent_data_providers: |_, _| async move {
Ok(pezsp_timestamp::InherentDataProvider::from_system_time())
},
defender: Mutex::new(NaiveEquivocationDefender::default()),
telemetry: None,
_authorities_tracker: AuthoritiesTracker::new(client.clone()),
};
let genesis = client.info().best_hash;
let mut sproof = RelayStateSproofBuilder::default();
sproof.included_para_head = Some(HeadData(client.header(genesis).unwrap().encode()));
sproof.para_id = pezcumulus_test_client::runtime::TEYRCHAIN_ID.into();
let validation_data = PersistedValidationData {
relay_parent_number: 1,
parent_head: client.header(genesis).unwrap().encode().into(),
..Default::default()
};
let block_builder = client.init_block_builder(Some(validation_data), sproof);
let block = block_builder.block_builder.build().unwrap();
let mut blocks = Vec::new();
for _ in 0..EQUIVOCATION_LIMIT + 1 {
blocks.push(seal_block(block.block.clone(), &client))
}
// sr25519 should generate a different signature every time you sign something and thus, all
// blocks get a different hash (even if they are the same block).
assert_eq!(blocks.iter().map(|b| b.hash()).collect::>().len(), blocks.len());
blocks.iter().take(EQUIVOCATION_LIMIT).for_each(|block| {
let mut params =
BlockImportParams::new(BlockOrigin::NetworkBroadcast, block.header().clone());
params.body = Some(block.extrinsics().to_vec());
verifier.verify(params).now_or_never().unwrap().unwrap();
});
// Now let's try some previously verified block and a block we have not verified yet.
//
// Verify should fail, because we are above the limit. However, when we change the origin to
// `ConsensusBroadcast`, it should work.
let extra_blocks =
vec![blocks[EQUIVOCATION_LIMIT / 2].clone(), blocks.last().unwrap().clone()];
extra_blocks.into_iter().for_each(|block| {
let mut params =
BlockImportParams::new(BlockOrigin::NetworkBroadcast, block.header().clone());
params.body = Some(block.extrinsics().to_vec());
assert!(verifier
.verify(params)
.now_or_never()
.unwrap()
.map(drop)
.unwrap_err()
.contains("excessive equivocations at slot"));
// When it comes from `pov-recovery`, we will accept it
let mut params =
BlockImportParams::new(BlockOrigin::ConsensusBroadcast, block.header().clone());
params.body = Some(block.extrinsics().to_vec());
assert!(verifier.verify(params).now_or_never().unwrap().is_ok());
});
}
}