1use crate::collators::RelayParentData;
29use codec::Codec;
30use cumulus_client_collator::service::ServiceInterface as CollatorServiceInterface;
31use cumulus_client_consensus_common::{
32 self as consensus_common, ParachainBlockImportMarker, ParachainCandidate,
33};
34use cumulus_client_parachain_inherent::{ParachainInherentData, ParachainInherentDataProvider};
35use cumulus_primitives_core::{
36 relay_chain::Hash as PHash, DigestItem, ParachainBlockData, PersistedValidationData,
37 RelayProofRequest,
38};
39use cumulus_relay_chain_interface::RelayChainInterface;
40use futures::prelude::*;
41use polkadot_node_primitives::{Collation, MaybeCompressedPoV};
42use polkadot_primitives::{Header as PHeader, Id as ParaId};
43use sc_client_api::BackendTransaction;
44use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy, StateAction};
45use sc_consensus_aura::standalone as aura_internal;
46use sc_network_types::PeerId;
47use sp_api::{ApiExt, ProofRecorder, ProvideRuntimeApi, StorageProof};
48use sp_application_crypto::AppPublic;
49use sp_consensus::{BlockOrigin, Environment, ProposeArgs, Proposer};
50use sp_consensus_aura::{AuraApi, Slot, SlotDuration};
51use sp_core::crypto::Pair;
52use sp_externalities::Extensions;
53use sp_inherents::{CreateInherentDataProviders, InherentData, InherentDataProvider};
54use sp_keystore::KeystorePtr;
55use sp_runtime::{
56 generic::Digest,
57 traits::{Block as BlockT, HashingFor, Header as HeaderT, Member},
58};
59use sp_state_machine::StorageChanges;
60use sp_timestamp::Timestamp;
61use sp_trie::proof_size_extension::ProofSizeExt;
62use std::{error::Error, time::Duration};
63
64pub struct Params<BI, CIDP, RClient, PF, CS> {
66 pub create_inherent_data_providers: CIDP,
68 pub block_import: BI,
70 pub relay_client: RClient,
72 pub keystore: KeystorePtr,
74 pub collator_peer_id: PeerId,
76 pub para_id: ParaId,
78 pub proposer: PF,
80 pub collator_service: CS,
83}
84
85pub struct BuildBlockAndImportParams<'a, Block: BlockT, P: Pair> {
87 pub parent_header: &'a Block::Header,
89 pub slot_claim: &'a SlotClaim<P::Public>,
91 pub additional_pre_digest: Vec<DigestItem>,
93 pub parachain_inherent_data: ParachainInherentData,
95 pub extra_inherent_data: InherentData,
97 pub proposal_duration: Duration,
99 pub max_pov_size: usize,
101 pub storage_proof_recorder: Option<ProofRecorder<Block>>,
106 pub extra_extensions: Extensions,
108}
109
110pub struct BuiltBlock<Block: BlockT> {
112 pub block: Block,
114 pub proof: StorageProof,
116 pub backend_transaction: BackendTransaction<HashingFor<Block>>,
120}
121
122impl<Block: BlockT> From<BuiltBlock<Block>> for ParachainCandidate<Block> {
123 fn from(built: BuiltBlock<Block>) -> Self {
124 Self { block: built.block, proof: built.proof }
125 }
126}
127
128pub struct Collator<Block, P, BI, CIDP, RClient, PF, CS> {
131 create_inherent_data_providers: CIDP,
132 block_import: BI,
133 relay_client: RClient,
134 keystore: KeystorePtr,
135 para_id: ParaId,
136 proposer: PF,
137 collator_service: CS,
138 _marker: std::marker::PhantomData<(Block, Box<dyn Fn(P) + Send + Sync + 'static>)>,
139}
140
141impl<Block, P, BI, CIDP, RClient, PF, CS> Collator<Block, P, BI, CIDP, RClient, PF, CS>
142where
143 Block: BlockT,
144 RClient: RelayChainInterface,
145 CIDP: CreateInherentDataProviders<Block, ()> + 'static,
146 BI: BlockImport<Block> + ParachainBlockImportMarker + Send + Sync + 'static,
147 PF: Environment<Block>,
148 CS: CollatorServiceInterface<Block>,
149 P: Pair,
150 P::Public: AppPublic + Member,
151 P::Signature: TryFrom<Vec<u8>> + Member + Codec,
152{
153 pub fn new(params: Params<BI, CIDP, RClient, PF, CS>) -> Self {
155 Collator {
156 create_inherent_data_providers: params.create_inherent_data_providers,
157 block_import: params.block_import,
158 relay_client: params.relay_client,
159 keystore: params.keystore,
160 para_id: params.para_id,
161 proposer: params.proposer,
162 collator_service: params.collator_service,
163 _marker: std::marker::PhantomData,
164 }
165 }
166
167 pub async fn create_inherent_data_with_rp_offset(
172 &self,
173 relay_parent: PHash,
174 validation_data: &PersistedValidationData,
175 parent_hash: Block::Hash,
176 timestamp: impl Into<Option<Timestamp>>,
177 relay_parent_descendants: Option<RelayParentData>,
178 relay_proof_request: RelayProofRequest,
179 collator_peer_id: PeerId,
180 ) -> Result<(ParachainInherentData, InherentData), Box<dyn Error + Send + Sync + 'static>> {
181 let paras_inherent_data = ParachainInherentDataProvider::create_at(
182 relay_parent,
183 &self.relay_client,
184 validation_data,
185 self.para_id,
186 relay_parent_descendants
187 .map(RelayParentData::into_inherent_descendant_list)
188 .unwrap_or_default(),
189 relay_proof_request,
190 collator_peer_id,
191 )
192 .await;
193
194 let paras_inherent_data = match paras_inherent_data {
195 Some(p) => p,
196 None => {
197 return Err(
198 format!("Could not create paras inherent data at {:?}", relay_parent).into()
199 )
200 },
201 };
202
203 let mut other_inherent_data = self
204 .create_inherent_data_providers
205 .create_inherent_data_providers(parent_hash, ())
206 .map_err(|e| e as Box<dyn Error + Send + Sync + 'static>)
207 .await?
208 .create_inherent_data()
209 .await
210 .map_err(Box::new)?;
211
212 if let Some(timestamp) = timestamp.into() {
213 other_inherent_data.replace_data(sp_timestamp::INHERENT_IDENTIFIER, ×tamp);
214 }
215
216 Ok((paras_inherent_data, other_inherent_data))
217 }
218
219 pub async fn create_inherent_data(
222 &self,
223 relay_parent: PHash,
224 validation_data: &PersistedValidationData,
225 parent_hash: Block::Hash,
226 timestamp: impl Into<Option<Timestamp>>,
227 relay_proof_request: RelayProofRequest,
228 collator_peer_id: PeerId,
229 ) -> Result<(ParachainInherentData, InherentData), Box<dyn Error + Send + Sync + 'static>> {
230 self.create_inherent_data_with_rp_offset(
231 relay_parent,
232 validation_data,
233 parent_hash,
234 timestamp,
235 None,
236 relay_proof_request,
237 collator_peer_id,
238 )
239 .await
240 }
241
242 pub async fn build_block_and_import(
244 &mut self,
245 params: BuildBlockAndImportParams<'_, Block, P>,
246 ) -> Result<Option<BuiltBlock<Block>>, Box<dyn Error + Send + 'static>> {
247 let Some((built_block, import_block)) = self.build_block(params).await? else {
248 return Ok(None);
249 };
250
251 self.import_block(import_block).await?;
252
253 Ok(Some(built_block))
254 }
255
256 pub async fn build_block(
258 &mut self,
259 mut params: BuildBlockAndImportParams<'_, Block, P>,
260 ) -> Result<
261 Option<(BuiltBlock<Block>, BlockImportParams<Block>)>,
262 Box<dyn Error + Send + 'static>,
263 > {
264 let mut digest = params.additional_pre_digest;
265 digest.push(params.slot_claim.pre_digest.clone());
266
267 let proposer = self
269 .proposer
270 .init(¶ms.parent_header)
271 .await
272 .map_err(|e| Box::new(e) as Box<dyn Error + Send>)?;
273
274 let mut inherent_data_combined = params.extra_inherent_data;
276 params
277 .parachain_inherent_data
278 .provide_inherent_data(&mut inherent_data_combined)
279 .await
280 .map_err(|e| Box::new(e) as Box<dyn Error + Send>)?;
281
282 let recorder_passed = params.storage_proof_recorder.is_some();
283 let storage_proof_recorder = params.storage_proof_recorder.unwrap_or_default();
284 let proof_size_ext_registered =
285 params.extra_extensions.is_registered(ProofSizeExt::type_id());
286
287 if !proof_size_ext_registered {
288 params
289 .extra_extensions
290 .register(ProofSizeExt::new(storage_proof_recorder.clone()));
291 } else if !recorder_passed {
292 return Err(
293 Box::from("`ProofSizeExt` registered, but no `storage_proof_recorder` provided. This is a bug.")
294 as Box<dyn Error + Send + Sync>
295 );
296 }
297
298 let propose_args = ProposeArgs {
300 inherent_data: inherent_data_combined,
301 inherent_digests: Digest { logs: digest },
302 max_duration: params.proposal_duration,
303 block_size_limit: Some(params.max_pov_size),
304 extra_extensions: params.extra_extensions,
305 storage_proof_recorder: Some(storage_proof_recorder.clone()),
306 };
307
308 let proposal = proposer
310 .propose(propose_args)
311 .await
312 .map_err(|e| Box::new(e) as Box<dyn Error + Send>)?;
313
314 let sealed_importable = seal::<_, P>(
315 proposal.block,
316 proposal.storage_changes,
317 ¶ms.slot_claim.author_pub,
318 &self.keystore,
319 )
320 .map_err(|e| e as Box<dyn Error + Send>)?;
321
322 let block = Block::new(
323 sealed_importable.post_header(),
324 sealed_importable
325 .body
326 .as_ref()
327 .expect("body always created with this `propose` fn; qed")
328 .clone(),
329 );
330
331 let Some(backend_transaction) = sealed_importable
332 .state_action
333 .as_storage_changes()
334 .map(|c| c.transaction.clone())
335 else {
336 tracing::error!(target: crate::LOG_TARGET, "Building a block should return storage changes!");
337
338 return Ok(None);
339 };
340
341 let proof = storage_proof_recorder.drain_storage_proof();
342
343 Ok(Some((BuiltBlock { block, proof, backend_transaction }, sealed_importable)))
344 }
345
346 pub async fn import_block(
348 &mut self,
349 import_block: BlockImportParams<Block>,
350 ) -> Result<(), Box<dyn Error + Send + 'static>> {
351 self.block_import
352 .import_block(import_block)
353 .map_err(|e| Box::new(e) as Box<dyn Error + Send>)
354 .await
355 .map(drop)
356 }
357
358 pub async fn collate(
367 &mut self,
368 parent_header: &Block::Header,
369 slot_claim: &SlotClaim<P::Public>,
370 additional_pre_digest: impl Into<Option<Vec<DigestItem>>>,
371 inherent_data: (ParachainInherentData, InherentData),
372 proposal_duration: Duration,
373 max_pov_size: usize,
374 scheduling_proof: Option<cumulus_primitives_core::SchedulingProof>,
375 ) -> Result<Option<(Collation, ParachainBlockData<Block>)>, Box<dyn Error + Send + 'static>> {
376 let maybe_candidate = self
377 .build_block_and_import(BuildBlockAndImportParams {
378 parent_header,
379 slot_claim,
380 additional_pre_digest: additional_pre_digest.into().unwrap_or_default(),
381 parachain_inherent_data: inherent_data.0,
382 extra_inherent_data: inherent_data.1,
383 proposal_duration,
384 max_pov_size,
385 storage_proof_recorder: None,
386 extra_extensions: Default::default(),
387 })
388 .await?;
389
390 let Some(candidate) = maybe_candidate else { return Ok(None) };
391
392 let hash = candidate.block.header().hash();
393 if let Some((collation, block_data)) = self.collator_service.build_collation(
394 parent_header,
395 hash,
396 candidate.into(),
397 scheduling_proof,
398 ) {
399 block_data.log_size_info();
400
401 if let MaybeCompressedPoV::Compressed(ref pov) = collation.proof_of_validity {
402 tracing::info!(
403 target: crate::LOG_TARGET,
404 "Compressed PoV size: {}kb",
405 pov.block_data.0.len() as f64 / 1024f64,
406 );
407 }
408
409 Ok(Some((collation, block_data)))
410 } else {
411 Err(Box::<dyn Error + Send + Sync>::from("Unable to produce collation"))
412 }
413 }
414
415 pub fn collator_service(&self) -> &CS {
417 &self.collator_service
418 }
419}
420
421pub struct SlotClaim<Pub> {
423 author_pub: Pub,
424 pre_digest: DigestItem,
425 slot: Slot,
426 timestamp: Timestamp,
427}
428
429impl<Pub> SlotClaim<Pub> {
430 pub fn unchecked<P>(author_pub: Pub, slot: Slot, timestamp: Timestamp) -> Self
435 where
436 P: Pair<Public = Pub>,
437 P::Public: Codec,
438 P::Signature: Codec,
439 {
440 SlotClaim { author_pub, timestamp, pre_digest: aura_internal::pre_digest::<P>(slot), slot }
441 }
442
443 pub fn author_pub(&self) -> &Pub {
445 &self.author_pub
446 }
447
448 pub fn pre_digest(&self) -> &DigestItem {
450 &self.pre_digest
451 }
452
453 pub fn slot(&self) -> Slot {
455 self.slot
456 }
457
458 pub fn timestamp(&self) -> Timestamp {
461 self.timestamp
462 }
463}
464
465pub async fn claim_slot<B, C, P>(
467 client: &C,
468 parent_hash: B::Hash,
469 relay_parent_header: &PHeader,
470 slot_duration: SlotDuration,
471 relay_chain_slot_duration: Duration,
472 keystore: &KeystorePtr,
473) -> Result<Option<SlotClaim<P::Public>>, Box<dyn Error>>
474where
475 B: BlockT,
476 C: ProvideRuntimeApi<B> + Send + Sync + 'static,
477 C::Api: AuraApi<B, P::Public>,
478 P: Pair,
479 P::Public: Codec,
480 P::Signature: Codec,
481{
482 let mut runtime_api = client.runtime_api();
483 runtime_api.set_call_context(sp_core::traits::CallContext::Onchain { import: false });
484 let authorities = runtime_api.authorities(parent_hash).map_err(Box::new)?;
485
486 let (slot_now, timestamp) = match consensus_common::get_relay_slot_and_timestamp(
488 relay_parent_header,
489 relay_chain_slot_duration,
490 ) {
491 Some((r_s, t)) => {
492 let our_slot = Slot::from_timestamp(t, slot_duration);
493 tracing::debug!(
494 target: crate::LOG_TARGET,
495 relay_slot = ?r_s,
496 para_slot = ?our_slot,
497 timestamp = ?t,
498 ?slot_duration,
499 ?relay_chain_slot_duration,
500 "Adjusted relay-chain slot to parachain slot"
501 );
502 (our_slot, t)
503 },
504 None => return Ok(None),
505 };
506
507 let author_pub = {
509 let res = aura_internal::claim_slot::<P>(slot_now, &authorities, keystore).await;
510 match res {
511 Some(p) => p,
512 None => return Ok(None),
513 }
514 };
515
516 Ok(Some(SlotClaim::unchecked::<P>(author_pub, slot_now, timestamp)))
517}
518
519pub fn seal<B: BlockT, P>(
521 pre_sealed: B,
522 storage_changes: StorageChanges<HashingFor<B>>,
523 author_pub: &P::Public,
524 keystore: &KeystorePtr,
525) -> Result<BlockImportParams<B>, Box<dyn Error + Send + Sync + 'static>>
526where
527 P: Pair,
528 P::Signature: Codec + TryFrom<Vec<u8>>,
529 P::Public: AppPublic,
530{
531 let (pre_header, body) = pre_sealed.deconstruct();
532 let pre_hash = pre_header.hash();
533 let block_number = *pre_header.number();
534
535 let block_import_params = {
537 let seal_digest =
538 aura_internal::seal::<_, P>(&pre_hash, &author_pub, keystore).map_err(Box::new)?;
539 let mut block_import_params = BlockImportParams::new(BlockOrigin::Own, pre_header);
540 block_import_params.post_digests.push(seal_digest);
541 block_import_params.body = Some(body);
542 block_import_params.state_action =
543 StateAction::ApplyChanges(sc_consensus::StorageChanges::Changes(storage_changes));
544 block_import_params.fork_choice = Some(ForkChoiceStrategy::LongestChain);
545 block_import_params
546 };
547 let post_hash = block_import_params.post_hash();
548
549 tracing::info!(
550 target: crate::LOG_TARGET,
551 "๐ Pre-sealed block for proposal at {}. Hash now {:?}, previously {:?}.",
552 block_number,
553 post_hash,
554 pre_hash,
555 );
556
557 Ok(block_import_params)
558}