{
/// The client instance.
client: Arc,
/// The backing network handle.
network: N,
/// Parachain collators.
collators: C,
/// handle to remote task executor
handle: TaskExecutor,
/// Store for extrinsic data.
availability_store: AvailabilityStore,
/// Live agreements. Maps relay chain parent hashes to attestation
/// instances.
live_instances: Mutex>>,
}
impl ParachainValidation where
C: Collators + Send + Unpin + 'static + Sync,
N: Network,
P: ProvideRuntimeApi + HeaderBackend + BlockBody + Send + Sync + 'static,
P::Api: ParachainHost + BlockBuilderApi + ApiExt,
C::Collation: Send + Unpin + 'static,
N::TableRouter: Send + 'static,
N::BuildTableRouter: Unpin + Send + 'static,
// Rust bug: https://github.com/rust-lang/rust/issues/24159
sp_api::StateBackendFor: sp_api::StateBackend>,
{
/// Get an attestation table for given parent hash.
///
/// This starts a parachain agreement process on top of the parent hash if
/// one has not already started.
///
/// Additionally, this will trigger broadcast of data to the new block's duty
/// roster.
fn get_or_instantiate(
&self,
parent_hash: Hash,
keystore: &KeyStorePtr,
max_block_data_size: Option,
)
-> Result, Error>
{
let mut live_instances = self.live_instances.lock();
if let Some(tracker) = live_instances.get(&parent_hash) {
return Ok(tracker.clone());
}
let id = BlockId::hash(parent_hash);
let validators = self.client.runtime_api().validators(&id)?;
let sign_with = signing_key(&validators[..], keystore);
let duty_roster = self.client.runtime_api().duty_roster(&id)?;
let (group_info, local_duty) = make_group_info(
duty_roster,
&validators,
sign_with.as_ref().map(|k| k.public()),
)?;
info!(
"Starting parachain attestation session on top of parent {:?}. Local parachain duty is {:?}",
parent_hash,
local_duty,
);
let active_parachains = self.client.runtime_api().active_parachains(&id)?;
debug!(target: "validation", "Active parachains: {:?}", active_parachains);
// If we are a validator, we need to store our index in this round in availability store.
// This will tell which erasure chunk we should store.
if let Some(ref local_duty) = local_duty {
if let Err(e) = self.availability_store.add_validator_index_and_n_validators(
&parent_hash,
local_duty.index,
validators.len() as u32,
) {
warn!(
target: "validation",
"Failed to add validator index and n_validators to the availability-store: {:?}", e
)
}
}
let table = Arc::new(SharedTable::new(
validators.clone(),
group_info,
sign_with,
parent_hash,
self.availability_store.clone(),
max_block_data_size,
));
let (_drop_signal, exit) = exit_future::signal();
let router = self.network.communication_for(
table.clone(),
&validators,
exit.clone(),
);
if let Some((Chain::Parachain(id), index)) = local_duty.as_ref().map(|d| (d.validation, d.index)) {
self.launch_work(parent_hash, id, router, max_block_data_size, validators.len(), index, exit);
}
let tracker = Arc::new(AttestationTracker {
table,
started: Instant::now(),
_drop_signal,
});
live_instances.insert(parent_hash, tracker.clone());
Ok(tracker)
}
/// Retain validation sessions matching predicate.
fn retain bool>(&self, mut pred: F) {
self.live_instances.lock().retain(|k, _| pred(k))
}
// launch parachain work asynchronously.
fn launch_work(
&self,
relay_parent: Hash,
validation_para: ParaId,
build_router: N::BuildTableRouter,
max_block_data_size: Option,
authorities_num: usize,
local_id: ValidatorIndex,
exit: exit_future::Exit,
) {
let (collators, client) = (self.collators.clone(), self.client.clone());
let availability_store = self.availability_store.clone();
let with_router = move |router: N::TableRouter| {
// fetch a local collation from connected collators.
let collation_work = collation_fetch(
validation_para,
relay_parent,
collators,
client.clone(),
max_block_data_size,
);
collation_work.map(move |result| match result {
Ok((collation, outgoing_targeted, fees_charged)) => {
match produce_receipt_and_chunks(
authorities_num,
&collation.pov,
&outgoing_targeted,
fees_charged,
&collation.info,
) {
Ok((receipt, chunks)) => {
// Apparently the `async move` block is the only way to convince
// the compiler that we are not moving values out of borrowed context.
let av_clone = availability_store.clone();
let chunks_clone = chunks.clone();
let receipt_clone = receipt.clone();
let res = async move {
if let Err(e) = av_clone.clone().add_erasure_chunks(
relay_parent.clone(),
receipt_clone,
chunks_clone,
).await {
warn!(target: "validation", "Failed to add erasure chunks: {}", e);
}
}
.unit_error()
.boxed()
.then(move |_| {
router.local_collation(collation, receipt, outgoing_targeted, (local_id, &chunks));
ready(())
});
Ok(Some(res))
}
Err(e) => {
warn!(target: "validation", "Failed to produce a receipt: {:?}", e);
Ok(None)
}
}
}
Err(e) => {
warn!(target: "validation", "Failed to collate candidate: {:?}", e);
Ok(None)
}
})
};
let router = build_router
.map_ok(with_router)
.map_err(|e| {
warn!(target: "validation" , "Failed to build table router: {:?}", e);
})
.and_then(|f| f)
.and_then(|f| match f {
Some(f) => f.map(Ok).boxed(),
None => ready(Ok(())).boxed(),
}).boxed();
let cancellable_work = select(exit, router).map(drop);
// spawn onto thread pool.
if self.handle.spawn(cancellable_work).is_err() {
error!("Failed to spawn cancellable work task");
}
}
}
/// Parachain validation for a single block.
struct AttestationTracker {
_drop_signal: exit_future::Signal,
table: Arc,
started: Instant,
}
/// Polkadot proposer factory.
pub struct ProposerFactory {
parachain_validation: Arc>,
transaction_pool: Arc,
keystore: KeyStorePtr,
_service_handle: ServiceHandle,
babe_slot_duration: u64,
_select_chain: SC,
max_block_data_size: Option,
backend: Arc,
}
impl ProposerFactory where
C: Collators + Send + Sync + Unpin + 'static,
C::Collation: Send + Unpin + 'static,
P: BlockchainEvents + BlockBody,
P: ProvideRuntimeApi + HeaderBackend + Send + Sync + 'static,
P::Api: ParachainHost +
BlockBuilderApi +
BabeApi +
ApiExt,
N: Network + Send + Sync + 'static,
N::TableRouter: Send + 'static,
N::BuildTableRouter: Send + Unpin + 'static,
TxPool: TransactionPool,
SC: SelectChain + 'static,
// Rust bug: https://github.com/rust-lang/rust/issues/24159
sp_api::StateBackendFor: sp_api::StateBackend>,
{
/// Create a new proposer factory.
pub fn new(
client: Arc,
_select_chain: SC,
network: N,
collators: C,
transaction_pool: Arc,
thread_pool: TaskExecutor,
keystore: KeyStorePtr,
availability_store: AvailabilityStore,
babe_slot_duration: u64,
max_block_data_size: Option,
backend: Arc,
) -> Self {
let parachain_validation = Arc::new(ParachainValidation {
client: client.clone(),
network,
collators,
handle: thread_pool.clone(),
availability_store: availability_store.clone(),
live_instances: Mutex::new(HashMap::new()),
});
let service_handle = crate::attestation_service::start(
client,
_select_chain.clone(),
parachain_validation.clone(),
thread_pool,
keystore.clone(),
max_block_data_size,
);
ProposerFactory {
parachain_validation,
transaction_pool,
keystore,
_service_handle: service_handle,
babe_slot_duration,
_select_chain,
max_block_data_size,
backend,
}
}
}
impl consensus::Environment for ProposerFactory where
C: Collators + Send + Unpin + 'static + Sync,
N: Network,
TxPool: TransactionPool + 'static,
P: ProvideRuntimeApi + HeaderBackend + BlockBody + Send + Sync + 'static,
P::Api: ParachainHost +
BlockBuilderApi +
BabeApi +
ApiExt,
C::Collation: Send + Unpin + 'static,
N::TableRouter: Send + 'static,
N::BuildTableRouter: Send + Unpin + 'static,
SC: SelectChain,
B: Backend> + 'static,
// Rust bug: https://github.com/rust-lang/rust/issues/24159
sp_api::StateBackendFor: sp_api::StateBackend> + Send,
{
type Proposer = Proposer;
type Error = Error;
fn init(
&mut self,
parent_header: &Header,
) -> Result {
let parent_hash = parent_header.hash();
let parent_id = BlockId::hash(parent_hash);
let tracker = self.parachain_validation.get_or_instantiate(
parent_hash,
&self.keystore,
self.max_block_data_size,
)?;
Ok(Proposer {
client: self.parachain_validation.client.clone(),
tracker,
parent_hash,
parent_id,
parent_number: parent_header.number,
transaction_pool: self.transaction_pool.clone(),
slot_duration: self.babe_slot_duration,
backend: self.backend.clone(),
})
}
}
/// The local duty of a validator.
#[derive(Debug)]
pub struct LocalDuty {
validation: Chain,
index: ValidatorIndex,
}
/// The Polkadot proposer logic.
pub struct Proposer {
client: Arc,
parent_hash: Hash,
parent_id: BlockId,
parent_number: BlockNumber,
tracker: Arc,
transaction_pool: Arc,
slot_duration: u64,
backend: Arc,
}
impl consensus::Proposer for Proposer where
TxPool: TransactionPool + 'static,
Client: ProvideRuntimeApi + HeaderBackend + Send + Sync + 'static,
Client::Api: ParachainHost + BlockBuilderApi + ApiExt,
Backend: sc_client_api::Backend> + 'static,
// Rust bug: https://github.com/rust-lang/rust/issues/24159
sp_api::StateBackendFor: sp_api::StateBackend> + Send,
{
type Error = Error;
type Transaction = sp_api::TransactionFor;
type Proposal = Pin<
Box<
dyn Future