1use cumulus_primitives_core::{
22 CollationInfo, CollectCollationInfo, ParachainBlockData, SchedulingProof,
23};
24
25use polkadot_primitives::UMP_SEPARATOR;
26use sc_client_api::BlockBackend;
27use sp_api::{ApiExt, ProvideRuntimeApi, StorageProof};
28use sp_consensus::BlockStatus;
29use sp_runtime::traits::{Block as BlockT, HashingFor, Header as HeaderT, Zero};
30
31use cumulus_client_consensus_common::ParachainCandidate;
32use polkadot_node_primitives::{BlockData, Collation, MaybeCompressedPoV, PoV};
33
34use codec::Encode;
35use std::sync::Arc;
36const LOG_TARGET: &str = "cumulus-collator";
38
39pub trait ServiceInterface<Block: BlockT> {
41 fn check_block_status(&self, hash: Block::Hash, header: &Block::Header) -> bool;
45
46 fn build_collation(
55 &self,
56 parent_header: &Block::Header,
57 block_hash: Block::Hash,
58 candidate: ParachainCandidate<Block>,
59 scheduling_proof: Option<SchedulingProof>,
60 ) -> Option<(Collation, ParachainBlockData<Block>)>;
61
62 fn build_multi_block_collation(
70 &self,
71 parent_header: &Block::Header,
72 blocks: Vec<Block>,
73 proof: StorageProof,
74 scheduling_proof: Option<SchedulingProof>,
75 ) -> Option<(Collation, ParachainBlockData<Block>)>;
76
77 fn announce_block(&self, block_hash: Block::Hash, data: Option<Vec<u8>>);
79}
80
81pub struct CollatorService<Block: BlockT, BS, RA> {
87 block_status: Arc<BS>,
88 announce_block: Arc<dyn Fn(Block::Hash, Option<Vec<u8>>) + Send + Sync>,
89 runtime_api: Arc<RA>,
90}
91
92impl<Block: BlockT, BS, RA> Clone for CollatorService<Block, BS, RA> {
93 fn clone(&self) -> Self {
94 Self {
95 block_status: self.block_status.clone(),
96 announce_block: self.announce_block.clone(),
97 runtime_api: self.runtime_api.clone(),
98 }
99 }
100}
101
102impl<Block, BS, RA> CollatorService<Block, BS, RA>
103where
104 Block: BlockT,
105 BS: BlockBackend<Block>,
106 RA: ProvideRuntimeApi<Block>,
107 RA::Api: CollectCollationInfo<Block>,
108{
109 fn split_at_separator(messages: Vec<Vec<u8>>) -> (Vec<Vec<u8>>, Vec<Vec<u8>>) {
110 let mut parts = messages.splitn(2, |m: &Vec<u8>| m.is_empty());
111 (parts.next().unwrap_or(&[]).to_vec(), parts.next().unwrap_or(&[]).to_vec())
112 }
113
114 pub fn new(
116 block_status: Arc<BS>,
117 announce_block: Arc<dyn Fn(Block::Hash, Option<Vec<u8>>) + Send + Sync>,
118 runtime_api: Arc<RA>,
119 ) -> Self {
120 Self { block_status, announce_block, runtime_api }
121 }
122
123 pub fn check_block_status(&self, hash: Block::Hash, header: &Block::Header) -> bool {
127 match self.block_status.block_status(hash) {
128 Ok(BlockStatus::Queued) => {
129 tracing::debug!(
130 target: LOG_TARGET,
131 block_hash = ?hash,
132 "Skipping candidate production, because block is still queued for import.",
133 );
134 false
135 },
136 Ok(BlockStatus::InChainWithState) => true,
137 Ok(BlockStatus::InChainPruned) => {
138 tracing::error!(
139 target: LOG_TARGET,
140 "Skipping candidate production, because block `{:?}` is already pruned!",
141 hash,
142 );
143 false
144 },
145 Ok(BlockStatus::KnownBad) => {
146 tracing::error!(
147 target: LOG_TARGET,
148 block_hash = ?hash,
149 "Block is tagged as known bad and is included in the relay chain! Skipping candidate production!",
150 );
151 false
152 },
153 Ok(BlockStatus::Unknown) => {
154 if header.number().is_zero() {
155 tracing::error!(
156 target: LOG_TARGET,
157 block_hash = ?hash,
158 "Could not find the header of the genesis block in the database!",
159 );
160 } else {
161 tracing::debug!(
162 target: LOG_TARGET,
163 block_hash = ?hash,
164 "Skipping candidate production, because block is unknown.",
165 );
166 }
167 false
168 },
169 Err(e) => {
170 tracing::error!(
171 target: LOG_TARGET,
172 block_hash = ?hash,
173 error = ?e,
174 "Failed to get block status.",
175 );
176 false
177 },
178 }
179 }
180
181 pub fn fetch_collation_info(
187 &self,
188 block_hash: Block::Hash,
189 header: &Block::Header,
190 ) -> Result<Option<(CollationInfo, u32)>, sp_api::ApiError> {
191 let runtime_api = self.runtime_api.runtime_api();
192
193 let api_version =
194 match runtime_api.api_version::<dyn CollectCollationInfo<Block>>(block_hash)? {
195 Some(version) => version,
196 None => {
197 tracing::error!(
198 target: LOG_TARGET,
199 "Could not fetch `CollectCollationInfo` runtime api version."
200 );
201 return Ok(None);
202 },
203 };
204
205 let collation_info = if api_version < 2 {
206 #[allow(deprecated)]
207 runtime_api
208 .collect_collation_info_before_version_2(block_hash)?
209 .into_latest(header.encode().into())
210 } else {
211 runtime_api.collect_collation_info(block_hash, header)?
212 };
213
214 Ok(Some((collation_info, api_version)))
215 }
216
217 fn build_multi_block_collation(
223 &self,
224 parent_header: &Block::Header,
225 blocks: Vec<Block>,
226 proof: StorageProof,
227 scheduling_proof: Option<SchedulingProof>,
228 ) -> Option<(Collation, ParachainBlockData<Block>)> {
229 let compact_proof =
230 match proof.into_compact_proof::<HashingFor<Block>>(*parent_header.state_root()) {
231 Ok(proof) => proof,
232 Err(e) => {
233 tracing::error!(target: "cumulus-collator", "Failed to compact proof: {:?}", e);
234 return None;
235 },
236 };
237
238 let api_version = self
244 .runtime_api
245 .runtime_api()
246 .api_version::<dyn CollectCollationInfo<Block>>(parent_header.hash())
247 .ok()
248 .flatten()?;
249 let mut upward_messages = Vec::new();
250 let mut upward_message_signals = Vec::<Vec<u8>>::with_capacity(4);
251 let mut horizontal_messages = Vec::new();
252 let mut new_validation_code = None;
253 let mut processed_downward_messages = 0;
254 let mut hrmp_watermark = None;
255 let mut head_data = None;
256
257 for block in &blocks {
258 let (collation_info, _api_version) = self
260 .fetch_collation_info(block.hash(), block.header())
261 .map_err(|e| {
262 tracing::error!(
263 target: LOG_TARGET,
264 error = ?e,
265 "Failed to collect collation info.",
266 )
267 })
268 .ok()
269 .flatten()?;
270
271 let (messages, signals) = Self::split_at_separator(collation_info.upward_messages);
272
273 upward_messages.extend(messages);
274 upward_message_signals.extend(signals);
275 horizontal_messages.extend(collation_info.horizontal_messages);
276
277 if let Some(new_code) = collation_info.new_validation_code {
278 if new_validation_code.replace(new_code).is_some() {
279 tracing::warn!(
280 target: LOG_TARGET,
281 block = ?block.hash(),
282 "Overwriting validation code from an earlier block in the bundle.",
283 );
284 }
285 }
286 processed_downward_messages += collation_info.processed_downward_messages;
287 hrmp_watermark = Some(collation_info.hrmp_watermark);
288 head_data = Some(collation_info.head_data);
289 }
290
291 horizontal_messages.sort_by(|a, b| a.recipient.cmp(&b.recipient));
293
294 let block_data = ParachainBlockData::<Block>::new(blocks, compact_proof, scheduling_proof);
295
296 let pov = polkadot_node_primitives::maybe_compress_pov(PoV {
297 block_data: BlockData(if api_version >= 3 {
298 block_data.encode()
299 } else {
300 let block_data = block_data.as_v0();
301
302 if block_data.is_none() {
303 tracing::error!(
304 target: LOG_TARGET,
305 "Trying to submit a collation with multiple blocks is not supported by the current runtime."
306 );
307 }
308
309 block_data?.encode()
310 }),
311 });
312
313 if !upward_message_signals.is_empty() {
315 upward_messages.push(UMP_SEPARATOR);
316 upward_messages.extend(upward_message_signals.into_iter());
317 }
318
319 let upward_messages = upward_messages
320 .try_into()
321 .map_err(|e| {
322 tracing::error!(
323 target: LOG_TARGET,
324 error = ?e,
325 "Number of upward messages should not be greater than `MAX_UPWARD_MESSAGE_NUM`",
326 )
327 })
328 .ok()?;
329 let horizontal_messages = horizontal_messages
330 .try_into()
331 .map_err(|e| {
332 tracing::error!(
333 target: LOG_TARGET,
334 error = ?e,
335 "Number of horizontal messages should not be greater than `MAX_HORIZONTAL_MESSAGE_NUM`",
336 )
337 })
338 .ok()?;
339
340 let collation = Collation {
341 upward_messages,
342 new_validation_code,
343 processed_downward_messages,
344 horizontal_messages,
345 hrmp_watermark: hrmp_watermark?,
347 head_data: head_data?,
348 proof_of_validity: MaybeCompressedPoV::Compressed(pov),
349 };
350
351 Some((collation, block_data))
352 }
353}
354
355impl<Block, BS, RA> ServiceInterface<Block> for CollatorService<Block, BS, RA>
356where
357 Block: BlockT,
358 BS: BlockBackend<Block>,
359 RA: ProvideRuntimeApi<Block>,
360 RA::Api: CollectCollationInfo<Block>,
361{
362 fn check_block_status(&self, hash: Block::Hash, header: &Block::Header) -> bool {
363 CollatorService::check_block_status(self, hash, header)
364 }
365
366 fn build_collation(
367 &self,
368 parent_header: &Block::Header,
369 _: Block::Hash,
370 candidate: ParachainCandidate<Block>,
371 scheduling_proof: Option<SchedulingProof>,
372 ) -> Option<(Collation, ParachainBlockData<Block>)> {
373 CollatorService::build_multi_block_collation(
374 self,
375 parent_header,
376 vec![candidate.block],
377 candidate.proof,
378 scheduling_proof,
379 )
380 }
381
382 fn announce_block(&self, block_hash: Block::Hash, data: Option<Vec<u8>>) {
383 (self.announce_block)(block_hash, data)
384 }
385
386 fn build_multi_block_collation(
387 &self,
388 parent_header: &<Block as BlockT>::Header,
389 blocks: Vec<Block>,
390 proof: StorageProof,
391 scheduling_proof: Option<SchedulingProof>,
392 ) -> Option<(Collation, ParachainBlockData<Block>)> {
393 CollatorService::build_multi_block_collation(
394 self,
395 parent_header,
396 blocks,
397 proof,
398 scheduling_proof,
399 )
400 }
401}