Files
pezkuwi-sdk/pezcumulus/client/collator/src/service.rs
T
pezkuwichain b6d35f6faf chore: add Dijital Kurdistan Tech Institute to copyright headers
Updated 4763 files with dual copyright:
- Parity Technologies (UK) Ltd.
- Dijital Kurdistan Tech Institute
2025-12-27 21:28:36 +03:00

362 lines
12 KiB
Rust

// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
// This file is part of Pezcumulus.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// Pezcumulus 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.
// Pezcumulus 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 Pezcumulus. If not, see <https://www.gnu.org/licenses/>.
//! The Pezcumulus [`CollatorService`] is a utility struct for performing common
//! operations used in teyrchain consensus/authoring.
use pezcumulus_client_network::WaitToAnnounce;
use pezcumulus_primitives_core::{CollationInfo, CollectCollationInfo, TeyrchainBlockData};
use pezsc_client_api::BlockBackend;
use pezsp_api::{ApiExt, ProvideRuntimeApi};
use pezsp_consensus::BlockStatus;
use pezsp_core::traits::SpawnNamed;
use pezsp_runtime::traits::{Block as BlockT, HashingFor, Header as HeaderT, Zero};
use pezcumulus_client_consensus_common::TeyrchainCandidate;
use pezkuwi_pez_node_primitives::{
BlockData, Collation, CollationSecondedSignal, MaybeCompressedPoV, PoV,
};
use codec::Encode;
use futures::channel::oneshot;
use parking_lot::Mutex;
use std::sync::Arc;
/// The logging target.
const LOG_TARGET: &str = "pezcumulus-collator";
/// Utility functions generally applicable to writing collators for Pezcumulus.
pub trait ServiceInterface<Block: BlockT> {
/// Checks the status of the given block hash in the Teyrchain.
///
/// Returns `true` if the block could be found and is good to be build on.
fn check_block_status(&self, hash: Block::Hash, header: &Block::Header) -> bool;
/// Build a full [`Collation`] from a given [`TeyrchainCandidate`]. This requires
/// that the underlying block has been fully imported into the underlying client,
/// as implementations will fetch underlying runtime API data.
///
/// This also returns the unencoded teyrchain block data, in case that is desired.
fn build_collation(
&self,
parent_header: &Block::Header,
block_hash: Block::Hash,
candidate: TeyrchainCandidate<Block>,
) -> Option<(Collation, TeyrchainBlockData<Block>)>;
/// Inform networking systems that the block should be announced after a signal has
/// been received to indicate the block has been seconded by a relay-chain validator.
///
/// This sets up the barrier and returns the sending side of a channel, for the signal
/// to be passed through.
fn announce_with_barrier(
&self,
block_hash: Block::Hash,
) -> oneshot::Sender<CollationSecondedSignal>;
/// Directly announce a block on the network.
fn announce_block(&self, block_hash: Block::Hash, data: Option<Vec<u8>>);
}
/// The [`CollatorService`] provides common utilities for teyrchain consensus and authoring.
///
/// This includes logic for checking the block status of arbitrary teyrchain headers
/// gathered from the relay chain state, creating full [`Collation`]s to be shared with validators,
/// and distributing new teyrchain blocks along the network.
pub struct CollatorService<Block: BlockT, BS, RA> {
block_status: Arc<BS>,
wait_to_announce: Arc<Mutex<WaitToAnnounce<Block>>>,
announce_block: Arc<dyn Fn(Block::Hash, Option<Vec<u8>>) + Send + Sync>,
runtime_api: Arc<RA>,
}
impl<Block: BlockT, BS, RA> Clone for CollatorService<Block, BS, RA> {
fn clone(&self) -> Self {
Self {
block_status: self.block_status.clone(),
wait_to_announce: self.wait_to_announce.clone(),
announce_block: self.announce_block.clone(),
runtime_api: self.runtime_api.clone(),
}
}
}
impl<Block, BS, RA> CollatorService<Block, BS, RA>
where
Block: BlockT,
BS: BlockBackend<Block>,
RA: ProvideRuntimeApi<Block>,
RA::Api: CollectCollationInfo<Block>,
{
/// Create a new instance.
pub fn new(
block_status: Arc<BS>,
spawner: Arc<dyn SpawnNamed + Send + Sync>,
announce_block: Arc<dyn Fn(Block::Hash, Option<Vec<u8>>) + Send + Sync>,
runtime_api: Arc<RA>,
) -> Self {
let wait_to_announce =
Arc::new(Mutex::new(WaitToAnnounce::new(spawner, announce_block.clone())));
Self { block_status, wait_to_announce, announce_block, runtime_api }
}
/// Checks the status of the given block hash in the Teyrchain.
///
/// Returns `true` if the block could be found and is good to be build on.
pub fn check_block_status(&self, hash: Block::Hash, header: &Block::Header) -> bool {
match self.block_status.block_status(hash) {
Ok(BlockStatus::Queued) => {
tracing::debug!(
target: LOG_TARGET,
block_hash = ?hash,
"Skipping candidate production, because block is still queued for import.",
);
false
},
Ok(BlockStatus::InChainWithState) => true,
Ok(BlockStatus::InChainPruned) => {
tracing::error!(
target: LOG_TARGET,
"Skipping candidate production, because block `{:?}` is already pruned!",
hash,
);
false
},
Ok(BlockStatus::KnownBad) => {
tracing::error!(
target: LOG_TARGET,
block_hash = ?hash,
"Block is tagged as known bad and is included in the relay chain! Skipping candidate production!",
);
false
},
Ok(BlockStatus::Unknown) => {
if header.number().is_zero() {
tracing::error!(
target: LOG_TARGET,
block_hash = ?hash,
"Could not find the header of the genesis block in the database!",
);
} else {
tracing::debug!(
target: LOG_TARGET,
block_hash = ?hash,
"Skipping candidate production, because block is unknown.",
);
}
false
},
Err(e) => {
tracing::error!(
target: LOG_TARGET,
block_hash = ?hash,
error = ?e,
"Failed to get block status.",
);
false
},
}
}
/// Fetch the collation info from the runtime.
///
/// Returns `Ok(Some((CollationInfo, ApiVersion)))` on success, `Err(_)` on error or `Ok(None)`
/// if the runtime api isn't implemented by the runtime. `ApiVersion` being the version of the
/// [`CollectCollationInfo`] runtime api.
pub fn fetch_collation_info(
&self,
block_hash: Block::Hash,
header: &Block::Header,
) -> Result<Option<(CollationInfo, u32)>, pezsp_api::ApiError> {
let runtime_api = self.runtime_api.runtime_api();
let api_version =
match runtime_api.api_version::<dyn CollectCollationInfo<Block>>(block_hash)? {
Some(version) => version,
None => {
tracing::error!(
target: LOG_TARGET,
"Could not fetch `CollectCollationInfo` runtime api version."
);
return Ok(None);
},
};
let collation_info = if api_version < 2 {
#[allow(deprecated)]
runtime_api
.collect_collation_info_before_version_2(block_hash)?
.into_latest(header.encode().into())
} else {
runtime_api.collect_collation_info(block_hash, header)?
};
Ok(Some((collation_info, api_version)))
}
/// Build a full [`Collation`] from a given [`TeyrchainCandidate`]. This requires
/// that the underlying block has been fully imported into the underlying client,
/// as it fetches underlying runtime API data.
///
/// This also returns the unencoded teyrchain block data, in case that is desired.
pub fn build_collation(
&self,
parent_header: &Block::Header,
block_hash: Block::Hash,
candidate: TeyrchainCandidate<Block>,
) -> Option<(Collation, TeyrchainBlockData<Block>)> {
let block = candidate.block;
let compact_proof = match candidate
.proof
.into_compact_proof::<HashingFor<Block>>(*parent_header.state_root())
{
Ok(proof) => proof,
Err(e) => {
tracing::error!(target: "pezcumulus-collator", "Failed to compact proof: {:?}", e);
return None;
},
};
// Create the teyrchain block data for the validators.
let (collation_info, _api_version) = self
.fetch_collation_info(block_hash, block.header())
.map_err(|e| {
tracing::error!(
target: LOG_TARGET,
error = ?e,
"Failed to collect collation info.",
)
})
.ok()
.flatten()?;
// Workaround for: https://github.com/pezkuwichain/pezkuwi-sdk/issues/98
//
// We are always using the `api_version` of the parent block. The `api_version` can only
// change with a runtime upgrade and this is when we want to observe the old `api_version`.
// Because this old `api_version` is the one used to validate this block. Otherwise we
// already assume the `api_version` is higher than what the relay chain will use and this
// will lead to validation errors.
let api_version = self
.runtime_api
.runtime_api()
.api_version::<dyn CollectCollationInfo<Block>>(parent_header.hash())
.ok()
.flatten()?;
let block_data = TeyrchainBlockData::<Block>::new(vec![block], compact_proof);
let pov = pezkuwi_pez_node_primitives::maybe_compress_pov(PoV {
block_data: BlockData(if api_version >= 3 {
block_data.encode()
} else {
let block_data = block_data.as_v0();
if block_data.is_none() {
tracing::error!(
target: LOG_TARGET,
"Trying to submit a collation with multiple blocks is not supported by the current runtime."
);
}
block_data?.encode()
}),
});
let upward_messages = collation_info
.upward_messages
.try_into()
.map_err(|e| {
tracing::error!(
target: LOG_TARGET,
error = ?e,
"Number of upward messages should not be greater than `MAX_UPWARD_MESSAGE_NUM`",
)
})
.ok()?;
let horizontal_messages = collation_info
.horizontal_messages
.try_into()
.map_err(|e| {
tracing::error!(
target: LOG_TARGET,
error = ?e,
"Number of horizontal messages should not be greater than `MAX_HORIZONTAL_MESSAGE_NUM`",
)
})
.ok()?;
let collation = Collation {
upward_messages,
new_validation_code: collation_info.new_validation_code,
processed_downward_messages: collation_info.processed_downward_messages,
horizontal_messages,
hrmp_watermark: collation_info.hrmp_watermark,
head_data: collation_info.head_data,
proof_of_validity: MaybeCompressedPoV::Compressed(pov),
};
Some((collation, block_data))
}
/// Inform the networking systems that the block should be announced after an appropriate
/// signal has been received. This returns the sending half of the signal.
pub fn announce_with_barrier(
&self,
block_hash: Block::Hash,
) -> oneshot::Sender<CollationSecondedSignal> {
let (result_sender, signed_stmt_recv) = oneshot::channel();
self.wait_to_announce.lock().wait_to_announce(block_hash, signed_stmt_recv);
result_sender
}
}
impl<Block, BS, RA> ServiceInterface<Block> for CollatorService<Block, BS, RA>
where
Block: BlockT,
BS: BlockBackend<Block>,
RA: ProvideRuntimeApi<Block>,
RA::Api: CollectCollationInfo<Block>,
{
fn check_block_status(&self, hash: Block::Hash, header: &Block::Header) -> bool {
CollatorService::check_block_status(self, hash, header)
}
fn build_collation(
&self,
parent_header: &Block::Header,
block_hash: Block::Hash,
candidate: TeyrchainCandidate<Block>,
) -> Option<(Collation, TeyrchainBlockData<Block>)> {
CollatorService::build_collation(self, parent_header, block_hash, candidate)
}
fn announce_with_barrier(
&self,
block_hash: Block::Hash,
) -> oneshot::Sender<CollationSecondedSignal> {
CollatorService::announce_with_barrier(self, block_hash)
}
fn announce_block(&self, block_hash: Block::Hash, data: Option<Vec<u8>>) {
(self.announce_block)(block_hash, data)
}
}